Only open one accordion tab at one time - javascript

I have an accordion that works really well, it looks good on the site and works as it should. However, I'm trying to add some more JavaScript functionality to it, to make it more it look more professional.
Currently, the accordion allows you to have multiple panels open at one time i.e. if I open one tab, and then open another tab, both tabs will be open at the same time. And the only way to close these panels, is to re-click on the header.
What I would like is some JavaScript code that prevents multiple tabs from being open at one time, so if I click on a new panel, it should close the existing open panel first. Here is my HTML code for the accordion:
<div class="accordion"><b>Heading 1</b></div>
<div class="panel">
<p class="text-light">Text 1</p>
</div>
<div class="accordion"><b>Heading 2</b></div>
<div class="panel">
<p class="text-light">Text 2</p>
</div>
Here is my JavaScript code in a separate JavaScript file which currently allows multiple tabs to be open at one time
var acc = document.getElementsByClassName("accordion");
var i;
for (i = 0; i < acc.length; i++) {
acc[i].onclick = function() {
this.classList.toggle("active");
this.nextElementSibling.classList.toggle("show");
}
}
Not sure if you need all the CSS, but heres the CSS for showing the panel
div.panel.show {
display: block !important;
}
Hopefully someone can help! Thanks in advance!

To achieve this you need to reset the state of the accordion back to its original state on each click, before you set the required classes on the clicked elements. To do that you can extract functionality to set the class names in to their own function and call it as required. Try this:
var acc = document.getElementsByClassName("accordion");
var panel = document.getElementsByClassName('panel');
for (var i = 0; i < acc.length; i++) {
acc[i].onclick = function() {
var setClasses = !this.classList.contains('active');
setClass(acc, 'active', 'remove');
setClass(panel, 'show', 'remove');
if (setClasses) {
this.classList.toggle("active");
this.nextElementSibling.classList.toggle("show");
}
}
}
function setClass(els, className, fnName) {
for (var i = 0; i < els.length; i++) {
els[i].classList[fnName](className);
}
}
Working example

Close all opened tabs before opening selected tab. This will prevent multiple tabs from being open at one time.
// Get all Accordion and Panel
let accHeading = document.querySelectorAll(".accordion");
let accPanel = document.querySelectorAll(".accordion-panel");
for (let i = 0; i < accHeading.length; i++) {
// Execute whenever an accordion is clicked
accHeading[i].onclick = function() {
if (this.nextElementSibling.style.maxHeight) {
hidePanels(); // Hide All open Panels
} else {
showPanel(this); // Show the panel
}
};
}
// Function to Show a Panel
function showPanel(elem) {
hidePanels();
elem.classList.add("active");
elem.nextElementSibling.style.maxHeight = elem.nextElementSibling.scrollHeight + "px";
}
// Function to Hide all shown Panels
function hidePanels() {
for (let i = 0; i < accPanel.length; i++) {
accPanel[i].style.maxHeight = null;
accHeading[i].classList.remove("active");
}
}
* {box-sizing: border-box;}
/* Style the Headings that are used to open and close the accordion panel */
.accordion {
background-color: #eee;
color: #444;
cursor: pointer;
padding: 18px;
margin: 0;
font-weight: 300;
}
/* Change color of the heading and icon (on hover and click) */
.active, .accordion:hover, .accordion:hover::after {
background-color: #007eff;
color: white;
}
/* Add "plus" sign (+) after Accordion */
.accordion::after {
content: '\002B';
color: #777;
font-weight: bold;
float: right;
}
/* Add "minus" sign (-) after Accordion (when accordion is active) */
.active::after {
content: "\2212";
color: white;
}
/* Style the accordion panel */
.accordion-panel {
padding: 0 18px;
overflow: hidden;
max-height: 0;
transition: max-height 0.2s ease-out;
}
<div style="border: 1px solid lightgray;">
<h2 class="accordion">Section 1</h2>
<div class="accordion-panel">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>
<h2 class="accordion">Section 2</h2>
<div class="accordion-panel">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>
<h2 class="accordion">Section 3</h2>
<div class="accordion-panel">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>
<h2 class="accordion">Section 4</h2>
<div class="accordion-panel">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>
</div>

When selecting one item, you just need to hide all of them beforehand.
var acc = document.getElementsByClassName("accordion");
var i;
for (i = 0; i < acc.length; i++) {
acc[i].onclick = function() {
hideAll();
this.classList.toggle("active");
this.nextElementSibling.classList.toggle("show");
}
}
function hideAll() {
for (i = 0; i < acc.length; i++) {
acc[i].classList.toggle("active", false);
acc[i].nextElementSibling.classList.toggle("show", false);
}
}

var acc = document.getElementsByClassName("accordion");
var i;
var last;
for (i = 0; i < acc.length; i++) {
acc[i].onclick = function() {
if(last){
last.classList.toggle("active",false);
last.nextElementSibling.classList.toggle("show",false);
}
this.classList.toggle("active");
this.nextElementSibling.classList.toggle("show");
last=this;
}
}
variable last will track the last active accordion, so you don't need to iterate every accordion and panel again.

you have to cover the panel with accordion, so you can easily do that.
refer this
html
<div class="accordion"><b>Heading 1</b>
<div class="panel">
<p class="text-light">Text 1</p>
</div>
</div>
<div class="accordion"><b>Heading 2</b>
<div class="panel">
<p class="text-light">Text 2</p>
</div>
</div>
jquery:
$('.accordion').click(function() {
$(this).find('.panel').show();
$(this).siblings().find('.panel').hide();
});
Working example

Related

Animate/transition content smoothly when it appears (after click) with jQuery

I have a 'form' that displays text feedback (2 options) depending on how many checkboxes are selected. In principle this works however I would like to improve the animation/transition of how the text is displayed.
If you select all checkboxes, a p is slide into view. Any other amount you get a separate message which functions the same way. Currently this is 'hardcoded' to 5 checkboxes. Dunno if it's possible to make this more flexible based on a data attribute or something so if in future there's 6 - it's not a whole new block of JS?
But the main goal is when the message is displayed, it slides into view. However if you alter your answer and click 'submit' again, the animation is janky.
So I'd like to improve the transition between the update. I've done this elsewhere where it just slides open the first time (if that's easier) but I can't get it to work on this version.
Other implementation here: https://codepen.io/moy/pen/LYdoEBG
But I'd like both to be consistent if possible. So just a refresh/update/fade will be fine rather than both sliding. Unless it's possible to at least make the page/content below slide/push don't gracefully?
$(".form-feedback .btn").click(function () {
let checkboxes = $(this).parents(".form-feedback").find('input:checked').length
$(this).parents(".form-feedback").find(".form-feedback__reveal p").hide()
if(checkboxes == 5){
$(this).parents(".form-feedback").find(".form-feedback__reveal p[data-feedback='all']").slideDown(200)
}else{
$(this).parents(".form-feedback").find(".form-feedback__reveal p[data-feedback='some']").slideDown(200)
}
});
.hide {
display: none;
}
.label {
display: block;
margin-bottom: 24px;
}
.checkbox {
display: block;
margin-bottom: 12px;
}
.btn {
background: black;
border-radius: 24px;
color: white;
display: inline-flex;
padding: 12px 24px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="boxout">
<div class="form-feedback">
<label class="label">HACCP helps a food business to...</label>
<label class="checkbox"><input type="checkbox">Minimise risks in areas of food safety</label>
<label class="checkbox"><input type="checkbox">Identify critical control points</label>
<label class="checkbox"><input type="checkbox">Decide on actions to take if something goes wrong</label>
<label class="checkbox"><input type="checkbox">Ensure procedures are followed and are effective</label>
<label class="checkbox"><input type="checkbox">Ensure appropriate record keeping</label>
Submit
<div class="form-feedback__reveal">
<p class="hide" data-feedback="all">That’s right, HACCP supports a business in all of the areas listed.</p>
<p class="hide" data-feedback="some">That’s incorrect, HACCP actually supports a business in all of these areas.</p>
</div>
</div>
</div>
<div class="content">
<h4>Content below to check it gets pushed down smoothly</h4>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>
$(".form-feedback .btn").click(async function () {
let checkboxes = $(this).parents(".form-feedback").find('input:checked').length
$(this).parents(".form-feedback").find(".form-feedback__reveal p").slideUp(200)
await new Promise(res => { setTimeout(res, 200); });
$(this).parents(".form-feedback").find(".form-feedback__reveal p").hide()
if(checkboxes == 5){
$(this).parents(".form-feedback").find(".form-feedback__reveal p[data-feedback='all']").slideDown(200)
}else{
$(this).parents(".form-feedback").find(".form-feedback__reveal p[data-feedback='some']").slideDown(200)
}
});
.hide {
display: none;
}
.label {
display: block;
margin-bottom: 24px;
}
.checkbox {
display: block;
margin-bottom: 12px;
}
.btn {
background: black;
border-radius: 24px;
color: white;
display: inline-flex;
padding: 12px 24px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="boxout">
<div class="form-feedback">
<label class="label">HACCP helps a food business to...</label>
<label class="checkbox"><input type="checkbox">Minimise risks in areas of food safety</label>
<label class="checkbox"><input type="checkbox">Identify critical control points</label>
<label class="checkbox"><input type="checkbox">Decide on actions to take if something goes wrong</label>
<label class="checkbox"><input type="checkbox">Ensure procedures are followed and are effective</label>
<label class="checkbox"><input type="checkbox">Ensure appropriate record keeping</label>
Submit
<div class="form-feedback__reveal">
<p class="hide" data-feedback="all">That’s right, HACCP supports a business in all of the areas listed.</p>
<p class="hide" data-feedback="some">That’s incorrect, HACCP actually supports a business in all of these areas.</p>
</div>
</div>
</div>
<div class="content">
<h4>Content below to check it gets pushed down smoothly</h4>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>
This is getting toward the right direction I think. I've added an animation to close the slideDown.
You could also add a check to make sure it doesn't slide up and back down if the text isn't going to change
I think Jess' animation is what you're looking for, so this solution is based on
If you select all checkboxes, a p is slide into view. Any other amount you get a separate message which functions the same way. Currently this is 'hardcoded' to 5 checkboxes. Dunno if it's possible to make this more >flexible based on a data attribute or something so if in future there's 6 - it's not a whole new block of JS?
To ensure the onclick eventlistener on the .btn-element is handled properly, use target.preventDefault(). Seeing as the element is an <a>-element, clicking it will force your screenview to jump to the top. preventDefault() makes sure the viewport stays in place when you click on the link (although making it a <button> or <input type="submit"> is correct semantically and accessibility wise, unless it actually IS a link that will redirect the user)
When you say you want a "dynamic solution", I'm going to assume you want the message that appears when 5 inputs are selected to be shown if all inputs are selected, no matter the amount of input-elements there are.
You already have the variable checkboxes which contains the length of the amount of checkboxes that are checked. Just make a new, very similiar variable that holds the length of the total amount of checkboxes in the form, and in your if-statement, set those two variables to be equal to each other. This way, your message will always show if all of the checkboxes are checked.
$(".form-feedback .btn").click(function(e) {
// remove default behaviour of the <a>
e.preventDefault();
// variables that hold the length of checkboxes
let checkedCheckboxes = $(this).parents(".form-feedback").find('input:checked').length;
let checkboxes = $(this).parents(".form-feedback").find('input').length;
$(this).parents(".form-feedback").find(".form-feedback__reveal p").hide()
if (checkedCheckboxes === checkboxes) {
$(this).parents(".form-feedback").find(".form-feedback__reveal p[data-feedback='all']").slideDown(200)
} else {
$(this).parents(".form-feedback").find(".form-feedback__reveal p[data-feedback='some']").slideDown(200);
}
});
.hide {
display: none;
}
.label {
display: block;
margin-bottom: 24px;
}
.checkbox {
display: block;
margin-bottom: 12px;
}
.btn {
background: black;
border-radius: 24px;
color: white;
display: inline-flex;
padding: 12px 24px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="boxout">
<div class="form-feedback">
<label class="label">HACCP helps a food business to...</label>
<label class="checkbox"><input type="checkbox">Minimise risks in areas of food safety</label>
<label class="checkbox"><input type="checkbox">Identify critical control points</label>
<label class="checkbox"><input type="checkbox">Decide on actions to take if something goes wrong</label>
<label class="checkbox"><input type="checkbox">Ensure procedures are followed and are effective</label>
<label class="checkbox"><input type="checkbox">Ensure appropriate record keeping</label>
Submit
<div class="form-feedback__reveal">
<p class="hide" data-feedback="all">That’s right, HACCP supports a business in all of the areas listed.</p>
<p class="hide" data-feedback="some">That’s incorrect, HACCP actually supports a business in all of these areas.</p>
</div>
</div>
</div>
<div class="content">
<h4>Content below to check it gets pushed down smoothly</h4>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>

smooth transition on content change

I have a DIV of unknown height with content that can be changed from a javascript event. When the content changes, the DIV resizes to fit the new content. What I'm trying to do is have the DIV transition to the different height smoothly rather than abruptly.
I've tried adding this in to the DIV's CSS but it doesn't help. Not unless I set a specific height of the element.
transition: height 1s linear;
Is there a simple way to do this based on the content? Or do I have no choice but to write a javascript function and control it manually.?
This could be what you are looking for:
Animating a div while it fits to dynamically loaded content
I refactored their solution here to account for adding content, though you can add/remove/alter it any way you need. This will animate to new height and width with appended content, regardless of the amount of content added.
There is more to the code than this, but these are the basic steps of what's involved:
/* 1: Append Content to the animated element */
$(element).append(content);
/* 2: Set height & width to auto */
$(element).css({'width':'auto','height':'auto', 'padding':'15px 25px'});
/* 3: Set height & width to an initial value*/
$(element).css({'width':oldWidth+'px','height':oldHeight+'px'});
/* 4: Animate to the final values */
$(element).animate({
'width':contentWidth+'px',
'height':contentHeight+'px',
'padding':'15px 25px'
}, 500);
You can alter the content variable to be whatever html content you need, or change $(element).append() to $(element).html() to alter the contents completely instead of just appending items to them.
In that case,
create a separate transition class and add that class to that DIV along with the Javascript event.
document.getElementById('divID').className='CssTransitionClassName';
The basic strategy is to manually do what the browser refuses to:
calculate the full size of the element's contents, then CSS transition
the element to that explicit pixel size.
A Js solution which i prefer
// This is the important part!
function collapseSection(element) {
// get the height of the element's inner content, regardless of its actual size
var sectionHeight = element.scrollHeight;
// temporarily disable all css transitions
var elementTransition = element.style.transition;
element.style.transition = '';
// on the next frame (as soon as the previous style change has taken effect),
// explicitly set the element's height to its current pixel height, so we
// aren't transitioning out of 'auto'
requestAnimationFrame(function() {
element.style.height = sectionHeight + 'px';
element.style.transition = elementTransition;
// on the next frame (as soon as the previous style change has taken effect),
// have the element transition to height: 0
requestAnimationFrame(function() {
element.style.height = 0 + 'px';
});
});
// mark the section as "currently collapsed"
element.setAttribute('data-collapsed', 'true');
}
function expandSection(element) {
// get the height of the element's inner content, regardless of its actual size
var sectionHeight = element.scrollHeight;
// have the element transition to the height of its inner content
element.style.height = sectionHeight + 'px';
// when the next css transition finishes (which should be the one we just triggered)
element.addEventListener('transitionend', function(e) {
// remove this event listener so it only gets triggered once
element.removeEventListener('transitionend', arguments.callee);
// remove "height" from the element's inline styles, so it can return to its initial value
element.style.height = null;
});
// mark the section as "currently not collapsed"
element.setAttribute('data-collapsed', 'false');
}
document.querySelector('#toggle-button').addEventListener('click', function(e) {
var section = document.querySelector('.section.collapsible');
var isCollapsed = section.getAttribute('data-collapsed') === 'true';
if(isCollapsed) {
expandSection(section)
section.setAttribute('data-collapsed', 'false')
} else {
collapseSection(section)
}
});
.container .section {
overflow: hidden;
transition: height 0.3s ease-out;
height: auto;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 10vh 10vw;
font-family: arial;
}
p {
margin: 10px;
}
.container {
border: 3px solid #ffeb3b;
margin: 0 auto;
max-width: 300px;
border-radius: 3px;
}
.container .section {
border: 3px solid #4caf50;
}
button {
display: block;
--webkit-appearance: none;
font-size: 18px;
border: none;
border-radius: 3px;
background: #2196f3;
color: white;
margin: 15px auto;
padding: 15px;
cursor: pointer;
transition: box-shadow 0.2s ease-out;
}
button:hover {
box-shadow: 0px 0px 15px lightgrey;
}
button:active {
box-shadow: 0px 0px 10px lightgrey;
}
button:focus {
outline: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="container">
<div class="section">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>
<div class="section collapsible">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
</div>
<div class="section">
<p>Ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>
</div>
<button id="toggle-button">Toggle collapse</button>
You can read further Reference

Multiple radio buttons as filters

Problem
I am trying to create a double filter, where you can combine filters from multiple categories. For example, first category of filters are years, and the second one types of media. I want to make so that you can filter only through the years, media type or both year x type of media (Music form the 1960s). Also, I'm trying to keep selected filters highlighted, somehow so you can keep track of which ones are active (I tried to make them bold, and it works for first set of filters, but fails for the second. How do I solve this problem?
Codepen
https://codepen.io/erutuf/pen/ZPwdBq
Attempt
filterSelection("all")
function filterSelection(c) {
var x, i;
x = document.getElementsByClassName("filterDiv");
if (c == "all") c = "";
for (i = 0; i < x.length; i++) {
w3RemoveClass(x[i], "show");
if (x[i].className.indexOf(c) > -1) w3AddClass(x[i], "show");
}
}
function w3AddClass(element, name) {
var i, arr1, arr2;
arr1 = element.className.split(" ");
arr2 = name.split(" ");
for (i = 0; i < arr2.length; i++) {
if (arr1.indexOf(arr2[i]) == -1) {
element.className += " " + arr2[i];
}
}
}
function w3RemoveClass(element, name) {
var i, arr1, arr2;
arr1 = element.className.split(" ");
arr2 = name.split(" ");
for (i = 0; i < arr2.length; i++) {
while (arr1.indexOf(arr2[i]) > -1) {
arr1.splice(arr1.indexOf(arr2[i]), 1);
}
}
element.className = arr1.join(" ");
}
// Add active class to the current button (highlight it)
var btnContainer = document.getElementById("myBtnContainer");
var btns = btnContainer.getElementsByClassName("btn");
for (var i = 0; i < btns.length; i++) {
btns[i].addEventListener("click", function() {
var current = document.getElementsByClassName("active");
current[0].className = current[0].className.replace(" active", "");
this.className += " active";
});
}
.filterDiv {
float: left;
background-color: white;
color: black;
width: 37vw;
line-height: 100px;
text-align: center;
margin: 5px;
display: none;
margin-right: 15px;
}
.show {
display: block;
}
.container {
margin-top: 20px;
overflow: hidden;
}
/* Style the buttons */
.btn {
border: none;
outline: none;
padding: 0;
padding-right: 40px;
background-color: white;
cursor: pointer;
font-size: 16px;
font-weight: normal;
}
.btn:hover {}
.btn.active {
font-weight: bold;
}
.content {
font-size: 16px;
line-height: 20px;
text-align: left;
}
<div id="myBtnContainer" align="center" style="line-height: 20pt">
<!-- <button class="btn active" onclick="filterSelection('all')"> Show all</button> Show ALL -->
<button class="btn active" onclick="filterSelection('ShowAll')">Show all</button>
<button class="btn" onclick="filterSelection('1950')">1950</button>
<button class="btn" onclick="filterSelection('1960')">1960</button>
<button class="btn" onclick="filterSelection('1970')">1970</button>
</div>
<br>
<div id="myBtnContainer" align="center" style="line-height: 20pt">
<!-- <button class="btn active" onclick="filterSelection('all')"> Show all</button> Show ALL -->
<button class="btn" onclick="filterSelection('AllMedia')">All Media</button>
<button class="btn" onclick="filterSelection('Movies')">Movies</button>
<button class="btn" onclick="filterSelection('Music')">Music</button>
<button class="btn" onclick="filterSelection('Newspapers')">Newspapers</button>
</div>
<br><br>
<div class="filterDiv AllShapes ShowAll 1950 Newspapers">
<div class="content">
1950 Newspapers Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</div>
</div>
<div class="filterDiv AllShapes ShowAll 1960 Music">
<div class="content">
1960 Music Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</div>
</div>
<div class="filterDiv AllShapes ShowAll 1960 Newspapers">
<div class="content">
1960 Newspapers Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</div>
</div>
The simplest way would be to use <label> and type="radio" inputs.
You can style the inner <i> element, adjacent (+) to the :checked input.
Store the filter references (props) into a data-filterable="value1 value2 valueZ" space delimited.
The trick is to:
on change event, get the checked inputs values as Array.
Hide all elements (add a .is-hidden class)
get a filtered subset of element to show - based on whether all the checked values are present in the data-filterable props (also as array).
The below example will work for any number of filter-sets:
const el_filters = document.querySelectorAll('[name="year"], [name="type"]'),
el_filterable = document.querySelectorAll('[data-filterable]');
const applyFilter = () => {
// Filter checked inputs
const el_checked = [...el_filters].filter(el => el.checked && el.value);
// Collect checked inputs values to array
const filters = [...el_checked].map(el => el.value);
// Get elements to filter
const el_filtered = [...el_filterable].filter(el => {
const props = el.getAttribute('data-filterable').trim().split(/\s+/);
return filters.every(fi => props.includes(fi))
});
// Hide all
el_filterable.forEach(el => el.classList.add('is-hidden'));
// Show filtered
el_filtered.forEach(el => el.classList.remove('is-hidden'));
}
// Assign event listener
el_filters.forEach(el => el.addEventListener('change', applyFilter));
// Init
applyFilter();
/* FILTER INPUTS */
.filterInputs {
padding-bottom: 5px;
}
.filterInputs input {
display: none;
}
.filterInputs label {
display: inline-block;
position: relative;
padding: 10px 20px;
cursor: pointer;
user-select: none; /* prevent highlight */
}
.filterInputs i {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-shadow: inset 0 0 0 2px #0bf;
z-index: -1;
}
.filterInputs input:checked + i {
background: #0bf;
}
/* HELPER CLASSES */
.is-hidden {
display: none;
}
<div class="filterInputs">
<label><input type="radio" name="year" value="" checked><i></i>All years</label>
<label><input type="radio" name="year" value="1950"><i></i>1950</label>
<label><input type="radio" name="year" value="1960"><i></i>1960</label>
<label><input type="radio" name="year" value="1970"><i></i>1970</label>
</div>
<div class="filterInputs">
<label><input type="radio" name="type" value=""><i></i>All types</label>
<label><input type="radio" name="type" value="movies"><i></i>Movies</label>
<label><input type="radio" name="type" value="music" checked><i></i>Music</label>
<label><input type="radio" name="type" value="newspapers"><i></i>Newspapers</label>
</div>
<div data-filterable="1950 newspapers">1950 Newspapers</div>
<div data-filterable="1960 music">1960 Music</div>
<div data-filterable="1960 newspapers">1960 Newspapers</div>
I used the attribute selector([filter][group]) for this scenario,
(function(){
const selectedFilters = [];
// [].slice.call => converts HTMLCollection to Array
const yearFilters = [].slice.call(document.querySelectorAll('[filter][group="year"]'));
const mediaFilters = [].slice.call(document.querySelectorAll('[filter][group="media"]'));
const allFilters = yearFilters.concat(mediaFilters);
const filterContents = [].slice.call(document.querySelectorAll('.filterDiv'));
// add click event to all filters
allFilters.forEach((filter) => {
filter.addEventListener('click', filterToggle);
});
function filterToggle() {
const filter = this.getAttribute('filter');
const group = this.getAttribute('group');
resetMediaFilters();
if(group === 'year') {
resetYearFilters();
mediaFilters[0].classList.add('active');
}
this.classList.add('active');
applyFilter();
}
function resetYearFilters() {
yearFilters.forEach(filter => filter.classList.remove('active'));
}
function resetMediaFilters() {
mediaFilters.forEach(filter => filter.classList.remove('active'));
}
function resetFilterContent() {
filterContents.forEach(content => content.classList.remove('show'));
}
function applyFilter() {
const selectedFilters = [].slice.call(document.querySelectorAll('[filter].active'))
.map(filter => filter.getAttribute('filter'));
// class starts with number is a invalid query selector, so using attribute selector for class
const selector = ["filterDiv"].concat(selectedFilters).map(filter => '[class~="'+ filter +'"]').join('');
resetFilterContent();
document.querySelectorAll(selector).forEach(content => content.classList.add('show'));
}
// initialize
applyFilter();
})();
.filterDiv {
float: left;
background-color: white;
color: black;
width: 37vw;
line-height: 100px;
text-align: center;
margin: 5px;
display: none;
margin-right: 15px;
}
.show {
display: block;
}
.container {
margin-top: 20px;
overflow: hidden;
}
/* Style the buttons */
.btn {
border: none;
outline: none;
padding: 0;
padding-right: 40px;
background-color: white;
cursor: pointer;
font-size: 16px;
font-weight: normal;
}
.btn:hover {
}
.btn.active {
font-weight: bold;
}
.content{
font-size:16px;
line-height:20px;
text-align:left;
}
<div id="myBtnContainer" align="center" style="line-height: 20pt">
<button class="btn active" filter="ShowAll" group="year">Show all</button>
<button class="btn" filter="1950" group="year">1950</button>
<button class="btn" filter="1960" group="year">1960</button>
<button class="btn" filter="1970" group="year">1970</button>
</div>
<br>
<div id="myBtnContainer" align="center" style="line-height: 20pt">
<button class="btn active" filter="AllMedia" group="media">All Media</button>
<button class="btn" filter="Movies" group="media">Movies</button>
<button class="btn" filter="Music" group="media">Music</button>
<button class="btn" filter="Newspapers" group="media">Newspapers</button>
</div>
<br><br>
<div class="filterDiv AllMedia ShowAll 1950 Newspapers">
<div class="content">
1950 Newspapers Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</div>
</div>
<div class="filterDiv AllMedia ShowAll 1960 Music">
<div class="content">
1960 Music Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</div>
</div>
<div class="filterDiv AllMedia ShowAll 1960 Newspapers">
<div class="content">
1960 Newspapers Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</div>
</div>
You have a document.getElementsByClassName() function in javascript that may help you here.
DOCS: https://www.w3schools.com/jsref/met_document_getelementsbyclassname.asp
You can obtain an array of elements that match the class you want to target and you can then iterate over them to add/remove the hidden property or css class that makes them shown or hide.
The same can be applied for the buttons you want to make bolder.
Pseudocode example:
Click the RED filter
Iterate over all elements on the page and
If it has the RED class, make hidden property = false
Otherwise, make hidden property = true
Then, iterate over all buttons of type COLOUR
If it's the RED button, add it the css class for btn_active
Otherwise, remove the btn_active class
If the button clicked is ALL, the logic is a bit different but it's just a matter of playing with the if-elses.
Remember you also have the addClass and removeClass methods on each element. You don't have to convert the class list to an array of classes and iterate over them. It's much more simpler than that:
https://www.w3schools.com/howto/howto_js_remove_class.asp

How to put normal Javascript into VueJS

I am literally just trying to implement into a vuejs component. Can anyone help with on where to put the JavaScript so that it'll work? I'm new to vuejs and I'm still not sure how to set everything up in the script section so that it'll work.
var acc = document.getElementsByClassName("accordion");
var i;
for (i = 0; i < acc.length; i++) {
acc[i].addEventListener("click", function() {
this.classList.toggle("active");
var panel = this.nextElementSibling;
if (panel.style.maxHeight){
panel.style.maxHeight = null;
} else {
panel.style.maxHeight = panel.scrollHeight + "px";
}
});
}
.accordion {
background-color: #eee;
color: #444;
cursor: pointer;
padding: 18px;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 15px;
transition: 0.4s;
}
.active, .accordion:hover {
background-color: #ccc;
}
.accordion:after {
content: '\002B';
color: #777;
font-weight: bold;
float: right;
margin-left: 5px;
}
.active:after {
content: "\2212";
}
.panel {
padding: 0 18px;
background-color: white;
max-height: 0;
overflow: hidden;
transition: max-height 0.2s ease-out;
}
<h2>Accordion with symbols</h2>
<button class="accordion">Section 1</button>
<div class="panel">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>
<button class="accordion">Section 2</button>
<div class="panel">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>
Thanks!
Since you're trying to learn Vue I'd suggest you look into how to achieve your desired functionality with Vues own toolset instead of using native JS event handlers, document selects etc.
You can easily add event listeners for most common events with the v-on: directive (or # for short):
<button class="accordion" #click="myClickHandler">Section 1</button>
You can refer to other elements via the ref property which is then available in the Vue component under this.$refs:
<div class="panel" ref="myPanel">...
and in your myClickHandler method:
methods: {
myClickHandler() {
let panel = this.$refs.myPanel
... do stuff ....
}
}
This would be "the Vue way" of tackling your problem - which once you get used to it is really awesome and simple compared to native JS dom selections, eventlisteners etc.
I'd suggest you look more into how to achieve this via Vues tools instead of trying to force native JS and foregoing all of Vues simplicity.
You can put regular javascript in the script tag of your Vue component. It will execute just the same way a regular app.js file would execute.

Parallax div without a background image

Working on a parallax effect for a site. I've gotten the parallax effect to function properly using a background image but I've decided to change things up just a bit. Rather than using a bg image for the effect I was looking to apply the effect to an entire div but I can't seem to get this working using the entire div. Looking to apply the effect to everything inside the .section div while keeping the #subpanel / scroll-pane independently scrollable.
html -
<div class="col col-100">
<div class="col col-30">
<div class="section">
<div id="subpanel" class="nav_dialog displayed" style="height: 660px; left: ; display: block;">
<div class="close_link">
Close (x)
</div>
<div class="scroll-pane" style="overflow: hidden; padding: 0px; width: 475px;">
<div class="jspContainer" style="width: 475px; height: 620px;">
<div class="jspPane" style="padding: 0px 65px 0px 20px; top: 0px; width: 396px; font-size: 15px;">
<img src="http://s2.postimg.org/5uxqi0mgl/cats1.jpg" alt="">
<p> </p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</div>
<div class="jspVerticalBar">
<div class="jspCap jspCapTop"></div>
<div class="jspTrack">
<div class="jspDrag">
<div class="jspDragTop"></div>
<div class="jspDragBottom"></div>
</div>
</div>
<div class="jspCap jspCapBottom"></div>
</div>
</div>
</div>
</div>
</div>
</div>
JS -
<script>
$(function() {
$.fn.parallax = function(options){
var $$ = $(this);
offset = $$.offset();
var defaults = {
"start": 0,
"stop": offset.top + $$.height(),
"coeff": 0.95
};
var opts = $.extend(defaults, options);
return this.each(function(){
$(window).bind('scroll', function() {
windowTop = $(window).scrollTop();
if((windowTop >= opts.start) && (windowTop <= opts.stop)) {
newCoord = windowTop * opts.coeff;
//console.log($$)
$$.css({
"position": "0 "+ newCoord + "px"
});
}
});
});
};
$('.section').parallax({ "coeff":-0.65 });
$('.section .scroll-pane').parallax({ "coeff":2.55 });
})
</script>
<script>
$(function()
{
$('.scroll-pane').jScrollPane();
});
</script>
Hopefully that makes some sense.
Any help would be appreciated.
it seems you changed background-position to position.
Allowed values for position are absolute,fixed,relative,static & inherit
You are probably looking for something like this to move a complete div
$$.css({
"top": newCoord + "px"
});
or
$$.css({
"margin-top": newCoord + "px"
});

Categories