JavaScript loop throught and hide all divs except one - javascript

I have a big list of getElementsById that looks like this:
let ahSummerCamps = document.getElementById("aH-summerCamps");
let ahTrekking = document.getElementById("aH-trekking");
let bpLongDistanceHiking = document.getElementById("bp-longDistanceHiking");
let bpThruHiking = document.getElementById("bp-thruHiking");
let cOceanCruising = document.getElementById("c-oceanCruising");
let cRiverCruising = document.getElementById("c-riverCruising");
let eRecreationsEvent = document.getElementById("e-recreationsEvent");
let eSportsEvent = document.getElementById("e-sportsEvents");
let phEscortedTours = document.getElementById("ph-escortedTours");
let phIndependentTours = document.getElementById("ph-independentTours");
let sPhotographicSafari = document.getElementById("s-photographicSafari");
let sCyclingSafari = document.getElementById("s-cyclingSafari");
let sAsBackcountrySkiing = document.getElementById("sAs-backcountrySkiing");
let sAsDownhillSkiing = document.getElementById("sAs-downhillSkiing");
let vChildcare = document.getElementById("v-childcare");
let vConservationAndEnvironment = document.getElementById("v-conservationAndEnvironment");
I won't go into details, is there any way that I can loop throught all of them and for example hide all except bpLongDistanceHiking and bpThruHiking .
I had this function but that is not good way for sure:
function hideAllExceptbP() {
ahSummerCamps.style.display = "none";
ahTrekking.style.display = "none";
/* ... and for all of them like this except bpLongDistanceHiking and bpThruHiking */
}
So on that way I need function for every element like:
function hideAllExceptaH() {
/* ... + 10 lines */
function hideAllExceptC() {
/* ... + 10 lines */
function hideAllExceptE() {
/* ... + 10 lines */
Function
function choice() {
backpacking.addEventListener("click", () => {
hideBackpacking();
hideSafari();
hidePackageHoliday();
showBackpackingOptions();
console.log("Backpacking");
});
So as you can see from html I have 8 divs at start, and when user click on backpacking for example function should hide all divs except bpLongDistanceHiking and bpThruHiking
Full code calling function:
HTML:
<div class="row">
<!--main divs-->
<div style="background-color: #4cc9f0" class="column" id="adventureHolidays"><p>Adventure Holidays</p></div>
<div style="background-color: #4895ef" class="column" id="backpacking"><p>Backpacking</p></div>
<div style="background-color: #4361ee" class="column" id="cruiseHolidays"><p>Cruise Holidays</p></div>
<div style="background-color: #4361ee" class="column" id="eventTravel"><p>Event Travel</p>
</div>
<div style="background-color: #3a0ca3" class="column" id="packageHoliday"><p>Package Holiday</p></div>
<div style="background-color: #480ca8" class="column" id="safari"><p>Safari</p></div>
<div style="background-color: #560bad" class="column" id="skiingAndSnowboarding"><p>Skiing and Snowboarding</p>
</div>
<div style="background-color: #7209b7" class="column" id="volunteering"><p>Volunteering</p></div>
<!--end on main divs-->
<!--sub divs-->
<div style="background-color: #4cc9f0" class="column" id="aH-summerCamps"><p>Summer camps</p></div>
<div style="background-color: #4895ef" class="column" id="aH-trekking"><p>Trekking</p></div>
<div style="background-color: #4361ee" class="column" id="bP-longDistanceHiking"><p>Long Distance Hiking</p></div>
<div style="background-color: #4361ee" class="column" id="bP-thruHiking"><p>Thru Hiking</p></div>
<div style="background-color: #3a0ca3" class="column" id="c-oceanCruising"><p>Ocean Cruising</p></div>
<div style="background-color: #480ca8" class="column" id="c-riverCruising"><p>River Cruising</p></div>
<div style="background-color: #560bad" class="column" id="e-recreationsEvent"><p>Recreations Events</p></div>
<div style="background-color: #7209b7" class="column" id="e-sportsEvents"><p>Sports events</p></div>
<div style="background-color: #4cc9f0" class="column" id="pH-escortedTours"><p>Escorted Tours</p></div>
<div style="background-color: #4895ef" class="column" id="pH-independentTours"><p>Independent Tours</p></div>
<div style="background-color: #4361ee" class="column" id="s-photographicSafari"><p>Photographic Safari</p></div>
<div style="background-color: #4895ef" class="column" id="s-cyclingSafari"><p>Cycling Safari</p></div>
<div style="background-color: #3a0ca3" class="column" id="sAs-backcountrySkiing"><p>Backcountry skiing</p></div>
<div style="background-color: #560bad" class="column" id="sAs-downhillSkiing"><p>Downhill skiing</p></div>
<div style="background-color: #4361ee" class="column" id="v-childcare"><p>Childcare</p></div>
<div style="background-color: #4895ef" class="column" id="v-conservationAndEnvironment"><p>Conservation And
Environment</p></div>
</div>
<script type="text/javascript" th:src="#{/js/index.js}"></script>
js:
let adventureHolidays = document.getElementById("adventureHolidays");
let backpacking = document.getElementById("backpacking");
let cruiseHolidays = document.getElementById("cruiseHolidays");
let eventTravel = document.getElementById("eventTravel");
let packageHoliday = document.getElementById("packageHoliday");
let safari = document.getElementById("safari");
let skiingAndSnowboarding = document.getElementById("skiingAndSnowboarding");
let volunteering = document.getElementById("volunteering");
/*End of inserting main variables*/
/*sub variables*/
let ahSummerCamps = document.getElementById("aH-summerCamps");
let ahTrekking = document.getElementById("aH-trekking");
let bPLongDistanceHiking = document.getElementById("bP-longDistanceHiking");
let bPThruHiking = document.getElementById("bP-thruHiking");
let cOceanCruising = document.getElementById("c-oceanCruising");
let cRiverCruising = document.getElementById("c-riverCruising");
let eRecreationsEvent = document.getElementById("e-recreationsEvent");
let eSportsEvent = document.getElementById("e-sportsEvents");
let phEscortedTours = document.getElementById("ph-escortedTours");
let phIndependentTours = document.getElementById("ph-independentTours");
let sPhotographicSafari = document.getElementById("s-photographicSafari");
let sCyclingSafari = document.getElementById("s-cyclingSafari");
let sAsBackcountrySkiing = document.getElementById("sAs-backcountrySkiing");
let sAsDownhillSkiing = document.getElementById("sAs-downhillSkiing");
let vChildcare = document.getElementById("v-childcare");
let vConservationAndEnvironment = document.getElementById("v-conservationAndEnvironment");
/*end of subs variables*/
const divs = ["adventureHolidays", "backpacking", "cruiseHolidays", "eventTravel", "packageHoliday", "safari", "skiingAndSnowboarding", "volunteering", "aH-summerCamps", "aH-trekking", "bP-longDistanceHiking", "bP-thruHiking", "c-oceanCruising", "c-riverCruising", "e-recreationsEvent", "e-sportsEvents", "ph-escortedTours", "ph-independentTours", "s-photographicSafari", "s-cyclingSafari", "sAs-backcountrySkiing", "v-conservationAndEnvironment", "sAs-downhillSkiing", "v-childcare"]
.map(id => document.getElementById(id)); // gives you an array of those elements
function hideAllDivsExcept(id) {
for (const div of divs) div.hidden = !div.id === id;
}
window.onload = function () {
choice();
};
/* start of function choice for user clicks*/
function choice() {
backpacking.addEventListener("click", () => {
hideAllDivsExcept('adventureHolidays');
console.log("Backpacking");
})
}
CSS:
* {
box-sizing: border-box;
}
/* Create three equal columns that floats next to each other */
.column {
float: left;
width: 33.33%;
padding: 10px;
}
/* Clear floats after the columns */
.row:after {
content: "";
/*
display: table;
*/
clear: both;
}
I'm getting:
index.js:38 Uncaught TypeError: Cannot read properties of null (reading 'id')
at hideAllDivsExcept (index.js:38:47)
at HTMLDivElement.<anonymous> (index.js:51:9)
And that is this
function hideAllDivsExcept(id) {
this > for (const div of divs) div.hidden = !div.id === id;
}

One approach to the problem is as below, with explanatory comments in the code:
// defining a named function to handle clicks on the 'filter' elements, that takes one
// argument, a reference to the Event Object:
const showRelevant = (evt) =>{
// caching a reference to the element to which the function is bound as the
// event-handler:
let source = evt.currentTarget,
// retrieving the attribute-value from the custom data-* attribute, using the
// HTMLElement.dataset API:
category = source.dataset.category;
// retrieving all <div> elements that do not have a "data-category" attribute which
// are the children of a .row element; we iterate over that collection using
// NodeList.prototype.forEach():
document.querySelectorAll('.row > div:not([data-category])').forEach(
// using an Arrow function we pass in a reference to the current
// Node of the NodeList over which we're iterating:
(el) => {
// we split the element's id property-value at the '-' character,
// and retrieve the first element of the resulting Array:
let prefix = el.id.split('-')[0];
// if the chosen category is exactly equal to 'all':
if (category === 'all') {
// we want to show all elements, therefore we set the hidden property
// of every element to false; so every element is shown:
el.hidden = false;
} else {
// otherwise, if the category is not equal to the prefix (above) the
// assessment results in true and so the element is hidden; if the
// category is equal to the prefix (therefore the current element is
// one we wish to see) the assessment results in false, and so the
// element is shown:
el.hidden = category !== prefix;
}
})
};
// here we retrieve all child elements with a "data-category" attribute of a .row element,
// and iterate over that NodeList with NodeList.prototype.forEach():
document.querySelectorAll('.row > [data-category]').forEach(
// using an Arrow function we pass in a reference to the current Node of the NodeList,
// and use EventTarget.addEventListener() to bind the showRelevant() function (note the
// deliberate lack of parentheses) as the event-handler for the 'click' event:
(el) => el.addEventListener('click', showRelevant)
)
:root {
--h-1: 212;
--h-2: 194;
--s: 84%;
--l: 62%;
--c-l: var(--l);
--bgc-l: var(--l);
}
* {
box-sizing: border-box;
}
.row {
/* rather than using float, I've used display: flex: */
display: flex;
/* the default of flex elements is:
flex-direction: row;
flex-wrap: nowrap;
flex-flow is a shorthand to update both those values;
here I've specified:
flex-direction: row;
flex-wrap: wrap;
*/
flex-flow: row wrap;
/* adjust to taste, but this replicates the behaviour of
floating in that elements on an incomplete row will
align to the start of the row: */
justify-content: start;
/* placing a 0.2em margin on the block-axis of the element;
in a language such as English (left-to-right, top-to-bottom)
that aligns to the top (margin-block-start) and bottom
(margin-block-end) margins: */
margin-block: 0.2em;
}
.row > * {
box-shadow: inset 0 0 0 3px currentColor, inset 0 0 0 4px #fff;
/* basing the width of the elements - using the flex-basis
property - to 33%: */
flex-basis: 33%;
padding: 10px;
}
.row > *:nth-child(even) {
/* using CSS custom properties to set color and
background-color: */
color: hsl(var(--h-1) var(--s) var(--c-l));
background-color: hsl(var(--h-2) var(--s) var(--bgc-l));
}
.row > *:nth-child(odd) {
color: hsl(var(--h-2) var(--s) var(--c-l));
background-color: hsl(var(--h-1) var(--s) var(--bgc-l));
}
.row > *:nth-child(even):hover,
.row > *:nth-child(odd):hover {
/* manipulating the CSS custom properties with the
calc function to make the color darker (by decreasing
the lightness) and the background-color lighter (by
increasing the lightness): */
--c-l: calc(var(--l)*0.5);
--bgc-l: calc(var(--l) * 1.2);
}
<div class="row">
<!--main divs-->
<!-- I've added a custom data-* attribute to each of the elements that would
constitute a 'filter' element; the attribute-value is all the initial
letters of the words written as camel-case, so:
'Adventure Holidays' => 'AH' => 'aH' -->
<div id="adventureHolidays" data-category="aH">
<p>Adventure Holidays</p>
</div>
<div id="backpacking" data-category="bP">
<p>Backpacking</p>
</div>
<div id="cruiseHolidays" data-category="cH">
<p>Cruise Holidays</p>
</div>
<div id="eventTravel" data-category="eT">
<p>Event Travel</p>
</div>
<div id="packageHoliday" data-category="pH">
<p>Package Holiday</p>
</div>
<div id="safari" data-category="s">
<p>Safari</p>
</div>
<div id="skiingAndSnowboarding" data-category="sAS">
<p>Skiing and Snowboarding</p>
</div>
<div id="volunteering" data-category="v">
<p>Volunteering</p>
</div>
<div data-category="all">
<p>Show all</p>
</div>
<!--end on main divs-->
</div>
<!-- this isn't essential, but I prefer to separate the 'filters' from the elements to be filtered: -->
<div class="row">
<!--sub divs-->
<!-- I've changed the id prefix of these elements to match the 'data-category' value from the
'filter' <div> elements: -->
<div id="aH-summerCamps">
<p>Summer camps</p>
</div>
<div id="aH-trekking">
<p>Trekking</p>
</div>
<div id="bP-longDistanceHiking">
<p>Long Distance Hiking</p>
</div>
<div id="bP-thruHiking">
<p>Thru Hiking</p>
</div>
<div id="cH-oceanCruising">
<p>Ocean Cruising</p>
</div>
<div id="cH-riverCruising">
<p>River Cruising</p>
</div>
<div id="eT-recreationsEvent">
<p>Recreations Events</p>
</div>
<div id="eT-sportsEvents">
<p>Sports events</p>
</div>
<div id="pH-escortedTours">
<p>Escorted Tours</p>
</div>
<div id="pH-independentTours">
<p>Independent Tours</p>
</div>
<div id="s-photographicSafari">
<p>Photographic Safari</p>
</div>
<div id="s-cyclingSafari">
<p>Cycling Safari</p>
</div>
<div id="sAS-backcountrySkiing">
<p>Backcountry skiing</p>
</div>
<div id="sAS-downhillSkiing">
<p>Downhill skiing</p>
</div>
<div id="v-childcare">
<p>Childcare</p>
</div>
<div id="v-conservationAndEnvironment">
<p>Conservation and Environment</p>
</div>
</div>
JS Fiddle demo.
References:
Arrow functions.
data-* attributes.
document.querySelectorAll().
Event.
EventTarget.addEventListener().
HTMLElement.dataset API.
HTMLElement.hidden property.
NodeList.prototype.forEach().
String.prototype.split().

Add an id to your main div with class 'row', e.x. daDiv then iterate and perform whatever you want checking id attribute of each one
$('#daDiv').children('div').each(function () {
if ($(this).attr('id') !== 'bP-longDistanceHiking' && $(this).attr('id') !== 'bP-thruHiking') {
$(this).hide();
}
});
CodePen
Example
UPDATE
You can even make a function
function hideExcept(arr) {
$('#daDiv').children('div').each(function () {
if (!arr.includes($(this).attr('id')))
$(this).hide();
});
}
hideExcept(['bP-longDistanceHiking', 'bP-thruHiking']); // hide all except bP-longDistanceHiking and bP-thruHiking

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>

Click button to toggle class of parent element - pure javascript

I have multiple divs on the page with the class 'item' – I'd like to include a button within the div that when clicked will toggle append/remove the class 'zoom' on the 'item' div…
<div class="item">
<button class="zoomer"></button>
</div>
I've found plenty of code examples that target an id element, but struggling to find a solution that works with multiples of the same class element on one page.
Many thanks in advance!
You can use querySelectorAll to get all of the buttons and then you can use forEach so you can target the element's item parent.
// Get all the buttons
let zoomer_button = document.querySelectorAll('.zoomer');
// Loop through the buttons.
// Arrow function allows to pass the element
zoomer_button.forEach(button => {
// Add an event listener for a click on the button.
button.addEventListener('click', function(e) {
// the e is the event, and then you check what the target is, which is the button.
// then you can toggle a 'zoom' class on the parent 'item'
e.target.parentNode.classList.toggle('zoom');
});
});
.item.zoom {
background-color: blue;
}
<div class="item">
<button class="zoomer">button</button>
</div>
<div class="item">
<button class="zoomer">button</button>
</div>
<div class="item">
<button class="zoomer">button</button>
</div>
<div class="item">
<button class="zoomer">button</button>
</div>
<div class="item">
<button class="zoomer">button</button>
</div>
If it's nested a layer deeper, you can use parentNode twice.
// Get all the buttons
let zoomer_button = document.querySelectorAll('.zoomer');
// Loop through the buttons.
// Arrow function allows to pass the element
zoomer_button.forEach(button => {
// Add an event listener for a click on the button.
button.addEventListener('click', function(e) {
// the e is the event, and then you check what the target is, which is the button.
// then you can toggle a 'zoom' class on the parent 'item'
e.target.parentNode.parentNode.classList.toggle('zoom');
});
});
.item.zoom {
background-color: blue;
}
<div class="item">
<div class="media">
<button class="zoomer">button</button>
</div>
</div>
<div class="item">
<div class="media">
<button class="zoomer">button</button>
</div>
</div>
<div class="item">
<div class="media">
<button class="zoomer">button</button>
</div>
</div>
<div class="item">
<div class="media">
<button class="zoomer">button</button>
</div>
</div>
You can use querySelectorAll and access each element with e.target
document.querySelectorAll('.item > .zoomer')
.forEach(elem => elem.addEventListener('click', e => {
e.target.classList.toggle('someClass')
}))
.someClass{
background:limegreen;
}
<div class="item">
<button class="zoomer">1</button>
</div>
<div class="item">
<button class="zoomer">2</button>
</div>
<div class="item">
<button class="zoomer">3</button>
</div>
<div class="item">
<button class="zoomer">4</button>
</div>
In the example below, are 7 <button>s that do various stuff -- details are commented in example.
// Render 7 <menu>/<button> combos
[...new Array(7)].forEach((item, index) => {
document.querySelector('main').insertAdjacentHTML('beforeend', `
<menu class="item${index}">
<button class="btn${index}">${index}</button>
</menu>`);
});
/*~~~~~~~~~~~~~~~~~~~~~~~.btn0*/
// Click <button> remove it's parent (which also removes the <button>)
document.querySelector('.btn0').onclick = function(e) {
this.parentElement.remove();
}
/*~~~~~~~~~~~~~~~~~~~~~~~.btn1*/
// Click <button> -- <button> is removed but it's contents is left behind
document.querySelector('.btn1').onclick = unWrap;
function unWrap(e) {
const clicked = e.target;
const parent = clicked.parentElement;
while (clicked.firstChild) {
parent.insertBefore(clicked.firstChild, clicked);
}
clicked.remove();
}
/*~~~~~~~~~~~~~~~~~~~~~.btn4-6*/
// Collect all tags with a class that starts with "btn"
const btns = document.querySelectorAll("[class^='btn']");
// Adding .target class to the last 2 <button>s
btns.forEach((btn, idx) => {
if (idx > 4) btn.classList.add('target')
});
/*~~~~~~~~~~~~~~~~~~~~~~~.btn2*/
// Target third <button> by index
/*
When <button> clicked, it's parent gets .hide class which is:
visibility:hidden which would normally hide the <button> as well, but
.btn2 has visibility explicitly set to visible
*/
btns[2].onclick = e => e.target.closest('menu').classList.toggle('hide');
/*~~~~~~~~~~~~~~~~~~~~~~~.btn3*/
/*
Everytime the <button> is clicked, a copy of itself is made and the
clones also have this ability as well
*/
btns[3].addEventListener('click', copySelf);
function copySelf(e) {
let dupe = e.target.cloneNode(true);
e.target.parentElement.append(dupe);
dupe.onclick = copySelf;
}
/*~~~~~~~~~~~~~~~~~~~~~.btn4-6*/
/*
The click event is bound to the parent/ancestor tag <section>
Any click to any <button> will trigger the event handler.
.btn4, .btn5, and .btn6 all react in a specific manner because
the event handler, delegateClick(e) is using flow control statements and
specific criteria.
*/
document.querySelector('main').onclick = delegateClick;
let armed = false;
function delegateClick(e) {
const clicked = e.target;
if (clicked.matches('button') && !armed) {
clicked.classList.add('armed');
armed = true;
return;
}
if (clicked.matches('.armed.target') && armed) {
clicked.parentElement.style.cssText = `font-size: 5rem; margin: 0`
clicked.replaceWith(`💥`);
return;
}
if (clicked.matches('.target') && armed) {
clicked.classList.add('armed');
return;
}
if (clicked.matches('.armed') && armed) {
clicked.classList.remove('armed');
armed = false;
}
}
menu {
outline: dashed red 1px;
}
.hide {
visibility: hidden;
}
.btn2 {
visibility: visible
}
.armed {
animation: warning 1s linear infinite;
}
#keyframes warning {
50% {
opacity: 0;
}
}
.target.armed {
background: red;
color: white;
}
button {
font: inherit;
cursor: pointer;
}
<main></main>

Swap 3 divs and make the selection div go to the center

I have three divs and I want that every single time I click on any div, it will be swapped with a second div which is meant to be at the center.
I have tried like this and it doesn't work:
function swapDiv(event, elem) {
elem.parentNode.insertBefore(elem, elem.parentNode.secondChild);
}
<div class="all-div-container">
<div class="div1" onclick="swapDiv(event,this);">
1
</div>
<div class="div2" onclick="swapDiv(event,this);">
2
</div>
<div class="div3" onclick="swapDiv(event,this);">
3
</div>
</div>
1 2 3 and when I click 3 the result must be 1 3 2 from this result and I click on 1 it's must be 3 1 2
function swapDiv(event, elem) {
// get all elements in .all-div-container
const allElements = [...elem.parentElement.children];
// get index of target elem
const targetIndex = allElements.indexOf(elem);
// get center element
const centerElem = allElements[1];
// exit from function if we clicked at center elem
if (elem === centerElem) return;
// move center element
if (targetIndex === 0) {
elem.parentElement.prepend(centerElem)
} else {
elem.parentElement.append(centerElem)
}
}
<div class="all-div-container">
<div class="div1" onclick="swapDiv(event,this);">
1
</div>
<div class="div2" onclick="swapDiv(event,this);">
2
</div>
<div class="div3" onclick="swapDiv(event,this);">
3
</div>
</div>
Consider the following.
$(function() {
$(".all-div-container > div").click(function(event) {
if ($(this).index() == 0) {
return false;
}
var $prev = $(this).prev();
var $self = $(this).detach();
$prev.before($self);
});
});
.all-div-container>div {
height: 2em;
width: 100%;
border: 1px solid #ccc;
border-radius: 3px;
margin-bottom: 3px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="all-div-container">
<div class="div1">1</div>
<div class="div2">2</div>
<div class="div3">3</div>
</div>
You did not clarify what should happen if the first item is clicked. This code will swap the clicked element and the previous element.

Removing all nodes from the DOM except for one subtree

I have a page structured like this:
<body>
<div class="one">
<div class="two">
<p class="three">Some text</p>
</div>
</div>
<div class="four">
<div class="five">
<p class="six">Some other text</p>
</div>
</div>
</body>
Given a selector, such as .five, I want to remove all elements from the DOM while preserving the hierarchy of .four > .five > .six. In other words, after deleting all the elements, I should be left with:
<body>
<div class="four">
<div class="five">
<p class="six">Some other text</p>
</div>
</div>
</body>
I came up with the following solution to this problem:
function removeElementsExcept(selector) {
let currentElement = document.querySelector(selector)
while (currentElement !== document.body) {
const parent = currentElement.parentNode
for (const element of parent.children) {
if (currentElement !== element) {
parent.removeChild(element)
}
}
currentElement = parent
}
}
This works well enough for the above case, for which I've created a JSfiddle.
However, when I try run it on a more complex web page such as on https://developer.mozilla.org/en-US/docs/Web/API/Node/removeChild with a call such as removeElementsExcept('#sect1'), I'd expect only the blue div containing the text "Note: As long as a reference ..." and its inner contents to be kept on the page. However, if you try to run this, lots of other elements are kept on the page along with the blue div as well.
What am I doing incorrectly in my function?
This happens because you are modifying the collection which is being iterated. You can work around this by manually adjusting the index being used to look at the children.
function removeElementsExcept(selector) {
let currentElement = document.querySelector(selector)
while (currentElement !== document.body) {
const parent = currentElement.parentNode;
let idx = 0;
while (parent.children.length > 1) {
const element = parent.children[idx];
if (currentElement !== element) {
parent.removeChild(element)
} else {
idx = 1;
}
}
currentElement = parent
}
}
parent.removeChild(element) changes the length of the iterated collection so elements are skipped. You can use [...parent.children] to spread the HTMLCollection into an array, making it safe for removals.
Another approach is building a set of nodes you want to keep by traversing all child nodes and all parent nodes from the target element. Then remove all other nodes that aren't in the set. I haven't run a benchmark.
const removeElementsExcept = el => {
const keptEls = new Set();
for (let currEl = el; currEl; currEl = currEl.parentNode) {
keptEls.add(currEl);
}
for (const childEl of [...el.querySelectorAll("*")]) {
keptEls.add(childEl);
}
for (const el of [...document.querySelectorAll("body *")]) {
if (!keptEls.has(el)) {
el.remove();
}
}
};
removeElementsExcept(document.querySelector(".five"));
.four {
background: red;
height: 100px;
padding: 1em;
}
.five {
background: blue;
height: 100px;
padding: 1em;
}
.six {
background: yellow;
height: 100px;
padding: 1em;
}
<div class="one">
<div class="two">
<p class="three">Some text</p>
</div>
</div>
<div class="four">
<div class="five">
<p class="six">Some other text</p>
</div>
</div>

How to change background color conditionally?

I am trying to change the color of background.
I am changing every odd results to light green(#f0f5f5) so when the result ends in even number,
I get big white space.
I would like to change background color of pagination section to light green when the result ends in even number.
Sear
search results displays only 5 results so it could be 2th and 4th.
search.addWidgets([
instantsearch.widgets.searchBox({
container: '#searchbox',
}),
instantsearch.widgets.hits({
container: '#Algolia_Result',
transformItems: function (items) {
return items.map(function (item) {
if (item.objectType === 'Startup') {
item._isDescription = isNotNull(item.description);
} else if (item.objectType === 'NEWS') {
item._isSource = isNotNull(item.source);
} else if (item.objectType === 'Comment') {
item._isComment = isNotNull(item.comment);
return item;
});
},
templates: {
empty: '<div id="empty">No results have been found for {{ query }}.</div><br>',
item: `
<a href="{{linkUrl}}" target="_blank">
<div class="algolia_container">
<div class="item1">
<div id="images"><img src="{{logoUrl}}" alt="{{hits-image}}" id="hits-image"></div>
<div id="objTyeps"><span class="objectType {{objectCss}}">{{objectType}}</span></div>
</div>
<div class="item2">
<div id="objectTitle">
<span id="titleForDisplay">{{#helpers.highlight}}{ "attribute": "titleForDisplay" }{{/helpers.highlight}}</span>
</div>
</div>
<div class="item3">
{{#_isLocation}}
<div id="location">{{#helpers.highlight}}{ "attribute": "location" }{{/helpers.highlight}}</div>
{{/_isLocation}}
</div>
</div></a>
`,
},
}),
instantsearch.widgets.pagination({
container: '#pagination',
}),
]);
#Algolia_Result > div > div > ol > li:nth-child(odd){background-color: #f0f5f5;}
.ais-Pagination-item {
display:inline;
padding: 5px;
margin: 0 5px;
border: 1px solid #E8E8E8;
border-radius:5px;
font-size:18px;
}
.ais-Pagination-list {
text-align: center;
height:45px;
padding-top: 10px;
}
.ais-Pagination-item:hover {
background-color: #DCDCDC;
transition: background-color .2s;
}
.ais-Pagination-item--selected{
background-color: #E8E8E8;
}
<div id="searchbox"></div>
<div id="results">
<div id="Algolia_Result"></div>
<div id="pagination"></div>
</div>
This is ok
This need be fixed as if the background color of pagination area is the same as the last result, it must be green
This is what I get in the console.
You can color background of the pagination row by using JavaScript to count the number of results and apply color if the number of results is even.
Check out the example below.
Example 1 is with an odd number of result rows and the CSS works fine, same as your working example.
Example 2 is with an even number of result rows and uses the JS code to style the pagination background.
// Count the rows
let numRows = document.querySelectorAll('#example-2 .row').length
// If the number of rows is even
if (numRows % 2 == 0) {
// Apply the background color to the pagination row
document.querySelector('#example-2 .pagination').style.backgroundColor = '#eee'
}
.container {
border: 1px solid #000;
margin-bottom: 5px;
}
.row:nth-child(odd) {
background-color: #eee;
}
Example 1
<div id="example-1" class="container">
<div>
<div class="row">Row 1</div>
<div class="row">Row 2</div>
<div class="row">Row 3</div>
</div>
<div>
<div class="pageination">Pagination Row</div>
</div>
</div>
Example 2
<div id="example-2" class="container">
<div>
<div class="row">Row 1</div>
<div class="row">Row 2</div>
<div>
<div>
<div class="pagination">Pagination Row</div>
</div>
</div>
EDIT: So in your example, you would add the following JavaScript.
<script>
let numRows = document.querySelectorAll('.ais-Hits-item').length
if (numRows % 2 == 0) {
document.querySelector('.ais-Pagination-list').style.backgroundColor = '#eee'
}
</script>
EDIT 2: Looking at your code sandbox I can see that the issue is that the JS that counts the number of rows is being run before the rows have been rendered by Algolia.
To solve this issue we need to place our row counting JS into an Algolia callback that is ran after the rows have been rendered. We can use the algolia search.on('render', ...) event callback.
Try this:
search.on('render', () => {
let numRows = document.querySelectorAll('.algolia_container').length;
if (numRows % 2 === 0) {
document.querySelector('#pagination').style.backgroundColor = 'red';
} else {
document.querySelector('#pagination').style.backgroundColor = 'transparent';
}
});

Categories

Resources