How can I write this javascript code cleaner? - javascript

all
I am currently practicing my coding skills and am making a simple footer selection webpage.
I have four footers with different looks that are set to "display:none" initially. Then, I have four buttons, each one corresponding to its footer type. When the button is clicked, it displays the footer.
Now I just want to know how do I write a cleaner Javascript than what I currently have. Thank you as always.
var footer1 = document.getElementById('footer1');
var footer2 = document.getElementById('footer2');
var footer3 = document.getElementById('footer3');
var footer4 = document.getElementById('footer4');
var btn1 = document.getElementById('btn1');
var btn2 = document.getElementById('btn2');
var btn3 = document.getElementById('btn3');
var btn4 = document.getElementById('btn4');
btn1.onclick = function(e) {
console.log('You clicked button1');
e.preventDefault();
footer1.style.display = 'block';
footer2.style.display = 'none';
footer3.style.display = 'none';
footer4.style.display = 'none';
}
btn2.onclick = function(e) {
console.log('You clicked button2');
e.preventDefault();
footer2.style.display = 'block';
footer1.style.display = 'none';
footer3.style.display = 'none';
footer4.style.display = 'none';
}
btn3.onclick = function(e) {
console.log('You clicked button3');
e.preventDefault();
footer3.style.display = 'block';
footer2.style.display = 'none';
footer1.style.display = 'none';
footer4.style.display = 'none';
}
btn4.onclick = function(e) {
console.log('You clicked button4');
e.preventDefault();
footer4.style.display = 'block';
footer2.style.display = 'none';
footer3.style.display = 'none';
footer1.style.display = 'none';
}

You can just use Arrays like this:
let buttons = [ 'btn1', 'btn2', 'btn3', 'btn4' ];
let footers = [ 'footer1', 'footer2', 'footer3', 'footer4' ];
buttons.forEach((btn, index) => {
// Please note that you might want to use addEventListener instead of onclick
document.getElementById(btn).addEventListener('click', (event) => {
event.preventDefault();
let button = 'button' + (index + 1);
alert('You clicked ' + button);
footers.forEach((footer, index_f) => {
let f = document.getElementById(footer);
if(index_f === index) {
f.style.display = 'block';
}
else {
f.style.display = 'none';
}
});
});
});
To make things even more interesting, you can play with querySelectorAll and custom attributes. You could, for example, add the classes custom-button to your buttons and custom-footer to your footers, and on each button add a data-footer attribute pointing to the id of the corresponding footer. Then, you could do this:
document.querySelectorAll(".custom-button").forEach((button) => {
button.addEventListener('click', (event) => {
document.querySelectorAll(".custom-footer").forEach(footer => footer.style.display = 'none');
let footer = button.getAttribute("data-footer");
document.getElementById(footer).style.display = 'block';
});
});
Quite shorter.

There is a common way to do this.
Share a class (foo) between all of the elements you want to group into your show/hide radio button-like functionality.
Then use one function, like
function handler = function(e) {
var foos = document.getElementsByClass("foo");
// make all foos display="none"
var target = targets[e.target]; //get the footer to show from the target button
target.style.display = "block";
}

You can use attribute selector to loop over elements.
[attributeName="value"]
^: Means starts with
$: Ends with
This way, your logic is generic and does not requires you to maintain any list of IDs
Idea:
You can create a pattern where every button id will correspond to visibility to necessary footer. Better idea would be to use data attribute(data-<attr name>) but you can work with ids for now.
Loop over all the buttons and add handler using addEventListener. onClick is a property, so assignment will erase/ override previous value. You can either have inline anonymous function or a named function if you have too many buttons.
In this handler, loop over all footers and hide them.
Fetch index using this.id and show necessary footer. Its always better to use classes for such actions instead of setting styles.
var buttons = document.querySelectorAll('[id^="btn"]');
Array.from(buttons, (button) => {
button.addEventListener('click', function(event) {
const footers = document.querySelectorAll('[id^="footer"]');
Array.from(footers, (footer) => footer.style.display = 'none' );
const index = this.id.match(/\d+/)[0];
document.getElementById(`footer${index}`).style.display = 'block';
})
})
div[id^="footer"] {
width: 100px;
height: 100px;
border: 1px solid gray;
margin: 10px;
}
<div id="footer1"> Footer 1 </div>
<div id="footer2"> Footer 2 </div>
<div id="footer3"> Footer 3 </div>
<div id="footer4"> Footer 4 </div>
<button id="btn1"> Button 1 </button>
<button id="btn2"> Button 2 </button>
<button id="btn3"> Button 3 </button>
<button id="btn4"> Button 4 </button>
References:
Attribute Selector - MDN

Related

Remove inline styles when clicking on button

The following happens to me:
I have made a slider with the next and previous arrows that works correctly. The case is that I have a button called "see all" in which when I press it, it puts a display none to the previous and next arrows and shows everything that is in the slider (that is to say, when I press the button, it shows everything and "deactivates" the slider).
The problem is that when you press the button again to stop displaying everything and return to "slider mode", the previous and next buttons do not go through the slider.
The slider is putting the active class and removing it to show what is inside. But once I have used the button of "see all" it is added in line a "display: none" and although I give to the arrows of previous or following and the class active is put correctly it remains the style="display:none;" inline in the html and it stops working.
The code of the button that shows everything:
var btnMore = document.querySelector(".btn-panes");
var boxes = document.querySelectorAll("#slider .box-panes");
var aLeft = document.querySelector("#slider .left");
var aRight = document.querySelector("#slider .right");
var btnMoreActivated = false;
btnMore.addEventListener("click", function(){
if(!btnMoreActivated){
for(var i=0; i<boxes.length; i++ ){
document.querySelector("#slider").style.flexWrap = "wrap";
boxes[i].style.display = "flex";
aLeft.classList.add("ocultar");
aRight.classList.add("ocultar");
}
btnMoreActivated = true;
} else {
for(var i=1; i<boxes.length; i++ ){
boxes[i].style.display = "none";
document.querySelector("#slider").style.flexFlow = "nowrap";
aLeft.classList.remove("ocultar");
aRight.classList.remove("ocultar");
}
btnMoreActivated = false;
}
});
The slider code (it works) but just to show you what it does in case you need to add something to fix the problem:
const items = document.querySelectorAll('#slider .box-panes');
const itemCount = items.length;
const nextItem = document.querySelectorAll('.right img');
const previousItem = document.querySelectorAll('.left img');
var count = 0;
function shorHide(){
switch (key) {
case value:
break;
default:
break;
}
}
function showNextItem() {
items[count].classList.remove('active');
if(count < itemCount - 1) {
count++;
} else {
count = 0;
}
items[count].classList.add('active');
}
function showPreviousItem() {
items[count].classList.remove('active');
if(count > 0) {
count--;
} else {
count = itemCount - 1;
}
items[count].classList.add('active');
}
function keyPress(e) {
e = e || window.event;
if (e.keyCode == '37') {
showPreviousItem();
} else if (e.keyCode == '39') {
showNextItem();
}
}
nextItem[0].addEventListener('click', showNextItem);
previousItem[0].addEventListener('click', showPreviousItem);
document.addEventListener('keydown', keyPress);
This is because inline styles are the most specific type of style to add and therefore the most difficult to override. The simplest solution is to not use inline styles and instead use a class that can be added or removed as needed.
Here's a simple example that you can use to replace: boxes[i].style.display = "none";
// Get the elements that need to be hidden/shown into a collection
let btns = document.querySelectorAll(".nav");
document.getElementById("hide").addEventListener("click", function(e){
// Loop over the collection
btns.forEach(function(el){
el.classList.add("hidden"); // Apply the hidden class
});
});
document.getElementById("show").addEventListener("click", function(e){
btns.forEach(function(el){
el.classList.remove("hidden"); // Remove the class
});
});
.hidden { display:none; }
<button id="hide">Hide Buttons</button>
<button id="show">Show Buttons</button>
<button class="nav"><<</button> <button class="nav">>></button>

ClassList toggle works only for some elements

I am an absolute beginner in this field. And what i m doing is a small todo-list page.
And I am stuck in the following 'button events' part.
As in the code, I want to toggle the class of 'completed' to the ancestor div of the button clicked. However, it seems to work only for some elements.
Meaning, if the nodelist of 'finishBtn' variable has an odd number length, the class list only toggles for even number indexes and vice versa.
For example, if nodelist.length = 3, then the class list only toggles for nodelist[0]
and nodelist[2].
(However, for removeBtn variable, it works just fine)
Thank you very much for your time. I would really appreciate every reply of yours.
Cos I m stuck in this bloody thing for hours.
addBtn.addEventListener('click', (e)=> {
e.preventDefault();
if(input.value === ''){
return;
}
// adding elements to the body;
const eachTodo = document.createElement('div');
eachTodo.classList.add('eachTodo');
const textName = document.createElement('p');
textName.textContent = input.value;
const btns = document.createElement('div');
btns.classList.add('btns');
const finish = document.createElement('button');
finish.classList.add('finish');
finish.textContent = 'Finish';
const remove = document.createElement('button');
remove.classList.add('remove');
remove.textContent = 'Remove';
btns.append(finish, remove);
eachTodo.append(textName, btns);
plansDiv.append(eachTodo);
input.value = '';
//button Events
const finishBtn = document.querySelectorAll('.finish');
const removeBtn = document.querySelectorAll('.remove');
finishBtn.forEach(btn => {
btn.addEventListener('click', ()=>{
btn.parentElement.parentElement.classList.toggle('completed');
})
})
removeBtn.forEach(btn => {
btn.addEventListener('click', ()=>{
btn.parentElement.parentElement.remove();
})
})
})
This is my CSS part
.completed p {
text-decoration: line-through;
opacity: 0.8;
}
As it stands you're querying all buttons on each add and adding new listeners to them all resulting in duplicate listeners on each button that fire sequentially.
Elements with an even number of listeners attached will toggle the class an even number of times and return to the initial value.
"on" toggle-> "off" toggle-> "on"
Elements with an odd number of attached listeners toggle the class an odd number of times and appear correct at the end.
"on" toggle-> "off" toggle-> "on" toggle-> "off"
(It works for Remove because the first listener removes the element and subsequent Removes don't change that.)
Add listeners only once to each button
You can avoid this by simply adding the listeners directly to the newly created buttons. You already have references to each button and their parent element (eachTodo) in the script, so you can just add the listeners directly to them and reference the parent directly.
finish.addEventListener('click', () => {
eachTodo.classList.toggle('completed');
});
remove.addEventListener('click', () => {
eachTodo.remove();
});
const addBtn = document.getElementById('addBtn');
const plansDiv = document.getElementById('plansDiv');
const input = document.getElementById('input');
addBtn.addEventListener('click', (e) => {
e.preventDefault();
if (input.value === '') {
return;
}
// adding elements to the body;
const eachTodo = document.createElement('div');
eachTodo.classList.add('eachTodo');
const textName = document.createElement('p');
textName.textContent = input.value;
const btns = document.createElement('div');
btns.classList.add('btns');
const finish = document.createElement('button');
finish.classList.add('finish');
finish.textContent = 'Finish';
const remove = document.createElement('button');
remove.classList.add('remove');
remove.textContent = 'Remove';
btns.append(finish, remove);
eachTodo.append(textName, btns);
plansDiv.append(eachTodo);
input.value = '';
// Add listeners directly
finish.addEventListener('click', () => {
eachTodo.classList.toggle('completed');
})
remove.addEventListener('click', () => {
eachTodo.remove();
})
})
.completed p {
text-decoration: line-through;
opacity: 0.8;
}
<input type="text" id="input">
<button type="button" id="addBtn">Add</button>
<div id="plansDiv"></div>
Event delegation
A more concise solution would be to use event delegation and handle all the buttons in a single handler added to the document. Here replacing parentElement with closest('.eachTodo') to avoid the fragility of a specific ancestor depth, and checking which button was clicked using Element.matches().
document.addEventListener('click', (e) => {
if (e.target.matches('button.finish')){
e.target.closest('.eachTodo').classList.toggle('completed');
}
if (e.target.matches('button.remove')){
e.target.closest('.eachTodo').remove();
}
});
const addBtn = document.getElementById('addBtn');
const plansDiv = document.getElementById('plansDiv');
const input = document.getElementById('input');
document.addEventListener('click', (e) => {
if (e.target.matches('button.finish')){
e.target.closest('.eachTodo').classList.toggle('completed');
}
if (e.target.matches('button.remove')){
e.target.closest('.eachTodo').remove();
}
});
addBtn.addEventListener('click', (e) => {
if (input.value === '') {
return;
}
// adding elements to the body;
const eachTodo = document.createElement('div');
eachTodo.classList.add('eachTodo');
const textName = document.createElement('p');
textName.textContent = input.value;
const btns = document.createElement('div');
btns.classList.add('btns');
const finish = document.createElement('button');
finish.classList.add('finish');
finish.textContent = 'Finish';
finish.type = 'button';
const remove = document.createElement('button');
remove.classList.add('remove');
remove.textContent = 'Remove';
remove.type = 'button';
btns.append(finish, remove);
eachTodo.append(textName, btns);
plansDiv.append(eachTodo);
input.value = '';
});
.completed p {
text-decoration: line-through;
opacity: 0.8;
}
<input type="text" id="input">
<button type="button" id="addBtn">Add</button>
<div id="plansDiv"></div>
Note that you can also avoid the necessity of e.preventDefault() by specifying type="button" when you create your buttons. see: The Button element: type

LocalStorage not saving onclick attributes

I am trying to make a calendar web application and want to save a users' state using localstorage. I have tried using two methods (innerHTML as well as a javascript DOM-to-JSON; https://github.com/azaslavsky/domJSON), but each removes the button's onclick event.
I believe the problem is that it is stringifying only the innerhtml, which for some reason doesnt have my onlick event listed (although the onlick event fires). What should I do so that a user can reload the page and still use the buttons.
Here is an example that shows the button stop working (delete cache and website cookies/data to make the button work again):
https://codepen.io/samuel-solomon/pen/XWdzKjd?editors=1011
window.pageState;
$(document).ready(function(){
let div = document.getElementById("div")
window.pageState = JSON.parse(localStorage.getItem('pageState'));
if (window.pageState === null) {
let btn = document.createElement('button')
btn.innerText = "Hello";
btn.style = "height: 100px";
btn.onclick = function (btn) {change_text(btn)}.bind(null, btn);
div.appendChild(btn);
window.pageState = {state: div.innerHTML};
}
else {div.innerHTML = window.pageState.state}
});
function change_text(btn) {
let div = document.getElementById("div");
if (btn.innerText === "Hello") {btn.innerText = "Goodbye";}
else if (btn.innerText === "Goodbye") {btn.innerText = "Hello";}
localStorage.setItem('pageState', JSON.stringify(window.pageState));
}
Here is my wbesite where the problem exists (Ex: double click 'Ae 101A' and double click it in the calender. it should disappear. Now add it again and reload the page. Now it doesnt disappear on double click):
https://turtle-pond.com/
It seems you are using jQuery. For hooking events to dynamically created elements, I suggest the selector filtering approach: instead of binding the click event on the button element itself, listen for the click event on the parent container (or even the document) and filter by the button's selector. Here is how:
window.pageState;
$(document).ready(function(){
let div = document.getElementById("div")
$(document).on('click', "#my-button", function (event) {
const btn = event.target; // or btn = document.getElementById("my-button");
change_text(btn);
});
window.pageState = JSON.parse(localStorage.getItem('pageState'));
if (window.pageState === null) {
let btn = document.createElement('button')
btn.innerText = "Hello";
btn.style = "height: 100px";
btn.id = "my-button";
div.appendChild(btn);
window.pageState = {state: div.innerHTML};
}
else {div.innerHTML = window.pageState.state}
});
function change_text(btn) {
let div = document.getElementById("div");
if (btn.innerText === "Hello") {btn.innerText = "Goodbye";}
else if (btn.innerText === "Goodbye") {btn.innerText = "Hello";}
localStorage.setItem('pageState', JSON.stringify(window.pageState));
}
Read more about this on jQuery documentation and here.

create a button that shows or hides a div depending on whether it is currently showing

I'm fairly new in with JS and was wondering, if
there is a more cleaner way of writing this code?
I'm trying to create a button that shows or hides a div depending on whether it is currently showing.
Many Thanks in Advance
Anne
var button = document.getElementById('button');
var hideText = document.getElementById('output').className = 'hide';
button.addEventListener('click', ()=>{
if(hideText){
document.getElementById('output').className = 'unhide';
hideText = false;
}else{
document.getElementById('output').className = 'hide';
hideText = true;
}
})
CSS
.hide{display: none;}
.unhide{display: block;}
Just use a single class (e.g. unhide) and make it invisible by default. Then do
button.addEventListener('click', () => {
document.getElementById('output').classList.toggle('unhide');
}
You can just toggle the classes
button.addEventListener('click', () => {
document.getElementById('output').classList.toggle('hide');
}
You can remove the extra variable hideText and also the extra css if you implement like this
var x = document.getElementById("output");
if (x.style.display === "none") {
x.style.display = "block";
} else {
x.style.display = "none";
}
In jquery the are other simpler possible way like toggle, hide, show.

How to call javascript function on all elements with the same classname

I am trying to get a pop-up window to display when a user clicks on an element on my page. I have all the functionality for the pop-up working correctly; however, I am having trouble getting the function to pop-up for every element that matches the specified classname. My code is below:
<script>
function descPop () {
// Description pop-up
// Get the modal
var modalprod = document.getElementById('myModalTwo');
// Get the button that opens the modal
var btnprod = document.getElementsByClassName("add-but")[0];
// Get the <span> element that closes the modal
var spanprod = document.getElementsByClassName("prod-close")[0];
// When the user clicks on the button, open the modal
btnprod.onclick = function() {
modalprod.style.display = "block";
}
// When the user clicks on <span> (x), close the modal
spanprod.onclick = function() {
modalprod.style.display = "none";
}
// When the user clicks anywhere outside of the modal, close it
window.onclick = function(event) {
if (event.target == modalprod) {
modalprod.style.display = "none";
}
}
}
</script>
Now, I know that currently this code is assigning index 0 to var btnprod; therefore, it will only pop-up when you click on the first element which matches the classname "add-but". How do I assign and access all elements, so that every tag with the classname "add-but" will pop-up the window?
Thanks
Solution:
function descPop () {
// Description pop-up
// Get the modal
var modalprod = document.getElementById('myModalTwo');
// Get the button that opens the modal
var btnprod = document.getElementsByClassName("add-but");
// Get the <span> element that closes the modal
var spanprod = document.getElementsByClassName("prod-close");
// When the user clicks on the button, open the modal
for (var i = 0; i < btnprod.length; i++) {
btnprod[i].onclick = function() {
modalprod.style.display = "block";
}
}
// When the user clicks on <span> (x), close the modal
for (var i = 0; i < spanprod.length; i++) {
spanprod[i].onclick = function() {
modalprod.style.display = "none";
}
}
// When the user clicks anywhere outside of the modal, close it
window.onclick = function(event) {
if (event.target == modalprod) {
modalprod.style.display = "none";
}
}
}
If Jquery is not an option, you could iterate over the HtmlCollection to do it:
var btnprod = document.getElementsByClassName("add-but"); // <- this gives you a HTMLCollection
for (var i = 0; i < btnprod.length; i++) {
btnprod[i].onclick = function() {
modalprod.style.display = "block";
}
}
you can change your code to the following:
$('.add-but').on('click', function() {
$(this).css('display', "none");
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="add-but">
fafafafafa
</div>
<br>
<div>
fafafaffafafaafafa
</div>
<br>
<div class="add-but">
fafafafafafafaafafafaffafa
</div>
note that i have changed the display to none instead of block for this example.
this uses the jQuery selectors, event binding and css function.
I'd prefer that sexier solution:
document.getElementsByName('add-but').forEach(btn => {
btn.onclick = (e) => {
btn.style.display = "block"
}
});
Same in one line:
document.getElementsByName('add-but').forEach(btn => btn.onclick = (e) => btn.style.display = "block" );
If you want have multiples clicks events on same btn:
document.getElementsByName('add-but').forEach(btn => btn.addEventListener('click', e => btn.style.display = "block" ));
You've added a jquery tag but there's no jquery here. With jquery you can just write:
$('.classname').on ('click', function () {
//activate modal
});
Alternatively, you can add them to a variable as a collection, referencing this variable will apply to any element with this class.
var x = $('.classname');

Categories

Resources