I've created a 3 way toggle with 3 states disabled
, default
, enabled
.
On clicking each input
, the corresponding div's should be displayed.
var content = function() {
var divs = ["div-data1", "div-data2", "div-data3"];
var visibleDivId = null;
function toggle() {
//code
}
function init() {
toggle();
}
return {
init: init,
}
}();
window.onload = function() {
content.init();
};
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/all.min.css" rel="stylesheet"/>
<div >
<input type="radio" name="toggle" value="false">
<label ><i ></i></label>
<input checked type="radio" name="toggle" value="-1">
<label ><i ></i></label>
<input type="radio" name="toggle" value="true">
<label ><i ></i></label>
</div>
<div style="display:none"> div1 </div>
<div style="display:none"> div2</div>
<div style="display:none">div3 </div>
How could the toggle works, without passing onclick
to HTML ?
Could someone please help.
Thanks.
CodePudding user response:
One approach is as follows, with explanatory comments in the code:
// a named function, using Arrow syntax, to handle the toggling; this passes
// a reference to the Event Object (automagically, from the later use of
// EventTarget.addEventListener()):
const toggle = (event) => {
// we retrieve the current element to which the event-handler was bound:
let current = event.currentTarget,
// we retrieve the parentNode of that element:
parent = current.parentNode,
// we use an Array-literal, with the spread operator, to create an
// Array of the parent-element's element-children
children = [...parent.children]
// and we filter that Array of elements with the anonymous Arrow
// function of the Array.prototype.filter() method:
.filter(
// here we pass 'el', a reference to the current Element of the
// Array of Elements we're iterating over, to retain only the
// elements which have a tagName exactly equal to the tagName
// of the current element:
(el) => current.tagName === el.tagName
),
// using Array.prototype.findIndex() to retrieve the index of the
// 'current' element from an Array containing it, and its siblings:
currentIndex = children.findIndex(
// here we again pass in a reference to the current element 'el'
// of the Array of elements, and retrieve the element which is
// the 'current' (variable-name) element:
(el) => el === current
);
// we use document.querySelectorAll() to retrieve the elements matching
// the selector stored in the element's 'data-group' attribute:
document.querySelectorAll(current.dataset.group)
// iterating over those elements, with NodeList.prototype.forEach():
.forEach(
// passing in a reference to the current element ('el'), and the
// index of the current element ('index'); here we update the
// opacity property, by assessing whether the 'index' variable
// is exactly-equal to the 'currentIndex' variable. If it is,
// we return 1 (so the element is fully visible), otherwise
// we return an invalid empty-string, which removes the
// opacity from the inline style attribute:
(el, index) => el.style.opacity = index === currentIndex ? 1 : ''
);
},
// creating a custom Event:
changeEvent = new Event('change');
// using document.querySelectorAll() to find all <input> elements inside of
// a .toggleGroup element, and iterating over that NodeList with
// NodeList.prototype.forEach():
document.querySelectorAll('.toggleGroup input').forEach(
// here we - again - pass in a reference to the current element ('el'),
// and use EventTarget.addEventListener() to bind the toggle() function
// (note the deliberate omission of the parentheses) as the event-
// handler for the 'change' event fired on the elements:
(el) => {
el.addEventListener('change', toggle);
// using a Yoda condition to see if the current element is exactly-equal
// to true (that way we can't accidentally use assignment ('=') instead
// of comparison ('==' or '===') without generating an error:
if (true === el.checked) {
// triggering the 'change' event in order to have the correct element
// show on page-load:
el.dispatchEvent(changeEvent);
}
}
);
*,
::before,
::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
main {
inline-size: clamp(15em, 50%, 900px);
margin-block: 1em;
margin-inline: auto;
}
.toggleGroup,
.groupToToggle {
display: flex;
gap: 1em;
justify-content: center;
margin-block: 1em;
}
.div-data {
border: 1px solid currentColor;
border-radius: 0.25em;
opacity: 0.2;
padding-block: 0.25em;
padding-inline: 0.5em;
transition: opacity 0.4s linear;
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css" type="text/css">
<main>
<div >
<!--
Added an id attribute, in order that we can associate the <input> with the <label>,
I also added a 'data-group' attribute, which contains an attribute-value which
serves as the CSS selector for the relevant group of elements:
-->
<input id="tw-toggle-1" type="radio" name="toggle" value="false" data-group=".div-data">
<!--
As above, the 'for' attribute (the attribute-value of which is identical to the
(unique) 'id' attribute of the relevant element in order to associate the <input>
and <label> together:
-->
<label for="tw-toggle-1" ><i ></i></label>
<input id="tw-toggle-2" checked type="radio" name="toggle" value="-1" data-group=".div-data">
<label for="tw-toggle-2" ><i ></i></label>
<input id="tw-toggle-3" type="radio" name="toggle" value="true" data-group=".div-data">
<label for="tw-toggle-3" ><i ></i></label>
</div>
<!--
Added a wrapper to group the related elements together:
-->
<div >
<!--
Added a 'div-data' class-name to easily target all elements with CSS, and
removed the inline styles preferring instead to move presentation entirely
to the CSS:
-->
<div >div1</div>
<div >div2</div>
<div >div3</div>
</div>
</main>
References:
- Array literals.
Array.prototype.filter()
.Array.prototype.findIndex()
.Array.prototype.forEach()
.- Arrow functions.
Element.children
.Event()
constructor.EventTarget.addEventListener()
.EventTarget.dispatchEvent()
.HTMLElement.style
.
CodePudding user response:
If you can't modify your HTML, it is possible to add click event listeners from within your JavaScript. You don't need to add the onclick
attribute to your HTML.
// Divs and toggles must have the same order. You can adjust the data structure here if this isn't desirable.
var divs = [".div-data1", ".div-data2", ".div-data3"];
var toggles = [".threeToggle1", ".threeToggle2", ".threeToggle3"];
var previouslySelectedDiv = null;
// Add click event handler to each toggle
for(let i = 0; i < toggles.length; i ) {
var toggleElem = document.querySelector(toggles[i]);
toggleElem.addEventListener("click", () => {
// Get the div to show and show it by removing the "display: none" styling
var divElem = document.querySelector(divs[i]);
divElem.style = "";
// Hide the previously shown div
if(previouslySelectedDiv !== null) {
previouslySelectedDiv.style = "display: none";
}
previouslySelectedDiv = divElem;
});
}
<div >
<input type="radio" name="toggle" value="false">
<label ><i ></i></label>
<input type="radio" name="toggle" value="-1">
<label ><i ></i></label>
<input type="radio" name="toggle" value="true">
<label ><i ></i></label>
</div>
<div style="display:none"> div1 </div>
<div style="display:none"> div2</div>
<div style="display:none">div3 </div>
With that said, there are probably better ways to do this if you can modify your HTML and/or aren't set on using the "click" event. One way to do this would be to store the query selector of the div which is to be displayed within the attributes of the input:
// Get all radio buttons which have a data-div attribute
const toggles = document.querySelectorAll('input[type="radio"][data-div]');
let lastSelectedDiv = null;
// Initialize shown/hidden state based on checked values
for(const toggle of toggles) {
const div = document.querySelector(toggle.getAttribute("data-div"));
if(toggle.checked) {
lastSelectedDiv = div;
} else {
div.style.display = "none";
}
}
// Add event listener to the radio group which hides the currently visible div and shows the new one.
document.querySelector("fieldset").addEventListener("change", (e) => {
if(lastSelectedDiv) {
lastSelectedDiv.style.display = "none";
}
const newDiv = document.querySelector(e.target.getAttribute("data-div"));
newDiv.style.display = "";
lastSelectedDiv = newDiv;
});
<fieldset >
<input type="radio" name="toggle" value="false" data-div=".div-data1">
<label ><i ></i></label>
<input checked type="radio" name="toggle" value="-1" data-div=".div-data2">
<label ><i ></i></label>
<input type="radio" name="toggle" value="true" data-div=".div-data3">
<label ><i ></i></label>
</fieldset>
<div > div1 </div>
<div > div2</div>
<div >div3 </div>