setInterval for multiple elments (Vanilla Javascript) - javascript

I have created a time tracker with vanilla Javascript; however, the start button functionality is not working correctly when you have more than one activity. If you have more than one activity div, the timer starts at the same time as the last div and the first divs play button starts the last div. Can you please look at my newPlay eventListener to determine how my approach was incorrect? (Am I missing a bind, this, event, or e?
JAVASCRIPT:
let date = new Date().toLocaleDateString('en-us', { weekday:"long", year:"numeric", month:"short", day:"numeric"});
let showDate = document.getElementById("insert-date");
showDate.innerText=date;
//Get number of activity buttons for loop to add event listener to each
const btnLength = document.querySelectorAll(".opt-btn").length;
//Will append new div's here
const mainDiv = document.getElementById("activities");
//Add event listeners to each activity button & captures the buttonValue(activity name)
for (let i = 0; i < btnLength; i++){
document.querySelectorAll(".opt-btn")[i].addEventListener("click", function() {
const buttonValue = this.value;
addActivity(buttonValue);
});
//Onclick event creates the div, i, p , play, stop, timer text and appends to the main div
function addActivity(buttonValue){
const newDiv = document.createElement('div');
newDiv.classList.add("activity");
const newP = document.createElement('p');
newP.innerText = buttonValue;
newP.classList.add("act-p");
newDiv.appendChild(newP);
const newPlay =document.createElement('i');
newPlay.classList.add("fa-solid");
newPlay.classList.add("fa-play");
let activityInterval;
newPlay.addEventListener('click', () => {
activityInterval = setInterval(() => {
elapsedSeconds++
updateTimerValue()
}, 1000)
})
newDiv.appendChild(newPlay);
const newStop =document.createElement('i');
newStop.classList.add("fa-solid");
newStop.classList.add("fa-circle-stop");
newStop.addEventListener("click", clearInterval(activityInterval));
newDiv.appendChild(newStop);
newElapsedTime = document.createElement('p');
newElapsedTime.classList.add("elapsedTime-Text");
newElapsedTime.innerText = "00:00:00";
newDiv.appendChild(newElapsedTime);
mainDiv.appendChild(newDiv);
let elapsedSeconds = 0;
updateTimerValue()
function updateTimerValue() {
// Count the seconds, minutes and hours, convert to strings and pad the strings with leading 0's
const seconds = String(elapsedSeconds % 60).padStart(2, '0')
const minutes = String(parseInt(elapsedSeconds / 60 % 60)).padStart(2, '0')
const hours = String(parseInt(elapsedSeconds / 60 / 60)).padStart(2, '0')
// Use string interpolation for formatting the timer string
newElapsedTime.innerText = `${hours}:${minutes}:${seconds}`
}
}
}
HTML:
<section class="option-sec">
<div class = "container">
<div class = "row">
<div class ="col col-md-2">
<button class = "opt-btn" value="BATHROOM"><img src ="/images/bathroom.png" class="option"></button>
</div>
<div class ="col col-md-2">
<button class = "opt-btn" value="COMMUTE"><img src ="/images/commute.png" class="option"></button>
</div>
<div class ="col col-md-2">
<button class = "opt-btn" value="WORK"><img src ="/images/desk.png" class="option"></button>
</div>
<div class ="col col-md-2">
<button class = "opt-btn" value="ENT"><img src ="/images/ent.png" class="option"></button>
</div>
<div class ="col col-md-2">
<button class = "opt-btn" value="FAMILY"><img src ="/images/family.png" class="option"></button>
</div>
<div class ="col col-md-2">
<button class = "opt-btn" value="FITNESS"><img src ="/images/gym.png" class="option"></button>
</div>
<div class ="col col-md-2">
<button class = "opt-btn" value="MEAL"><img src ="/images/meal.png" class="option"></button>
</div>
<div class ="col col-md-2">
<button class = "opt-btn" value="MEDITATE"><img src ="/images/meditate.png" class="option"></button>
</div>
<div class ="col col-md-2">
<button class = "opt-btn" value="RELIGION"><img src ="/images/pray.png" class="option"></button>
</div>
<div class ="col col-md-2">
<button class = "opt-btn" value="SHOPPING"><img src ="/images/shopping.png" class="option"></button>
</div>
<div class ="col col-md-2">
<button class = "opt-btn" value="SLEEP"><img src ="/images/sleep.png" class="option"></button>
</div>
<div class ="col col-md-2">
<button class = "opt-btn" value="STUDY"><img src ="/images/study.png" class="option"></button>
</div>
</div>
</div>
</section>
<!--End Of Option Buttons-->
<section class="date-area">
<h1 class="date-hdr">CURRENT DATE: </h1>
<h2 class="date-hdr" id="insert-date"></h2>
</section>
<div class ="activity-container">
<div id="activities" class="activity-list"></div>
</div>

TLDR;
Declare variables before use.
Writing the script in strict mode would have generate a compile time error that newElapsedTime has not been declared before use.
In sloppy mode however, assignment to newElapsedTime without declaration causes any activity's "play" click handler to create or overwrite the value of a shared global object property window.newElapsedTime with no error generated.
Declaring the variable in the execution context of the handler which added the activity to mainDiv seems to solve activity clicks interfering with each other.
This example of a solution does not implement stop (or pause and resume) and logs an error if the play <i> element is clicked multiple times for the same activity:
"use strict";
let date = new Date().toLocaleDateString('en-us', { weekday:"long", year:"numeric", month:"short", day:"numeric"});
let showDate = document.getElementById("insert-date");
showDate.innerText=date;
// debug: show value in buttons instead of images
Array.from(document.querySelectorAll(".opt-btn")).forEach(
button=>button.innerHTML = button.value
);
//Get number of activity buttons for loop to add event listener to each
const btnLength = document.querySelectorAll(".opt-btn").length;
//Will append new div's here
const mainDiv = document.getElementById("activities");
//Add event listeners to each activity button & captures the buttonValue(activity name)
for (let i = 0; i < btnLength; i++){
document.querySelectorAll(".opt-btn")[i].addEventListener("click", function() {
const buttonValue = this.value;
addActivity(buttonValue);
});
//Onclick event creates the div, i, p , play, stop, timer text and appends to the main div
function addActivity(buttonValue){
const newDiv = document.createElement('div');
newDiv.classList.add("activity");
const newP = document.createElement('p');
newP.innerText = buttonValue;
newP.classList.add("act-p");
newDiv.appendChild(newP);
const newPlay =document.createElement('i');
newPlay.classList.add("fa-solid");
newPlay.classList.add("fa-play");
let activityInterval;
newPlay.addEventListener('click', () => {
// what to do if newPlay clicked twice?
if( activityInterval) {
throw Error("no code support for clicking play multiple times for same activity");
}
activityInterval = setInterval(() => {
elapsedSeconds++
updateTimerValue()
}, 1000)
})
newDiv.appendChild(newPlay);
const newStop =document.createElement('i');
newStop.classList.add("fa-solid");
newStop.classList.add("fa-circle-stop");
newStop.addEventListener("click", clearInterval(activityInterval));
newDiv.appendChild(newStop);
// declare newElapsedTime variable befor use:
const newElapsedTime = document.createElement('p');
newElapsedTime.classList.add("elapsedTime-Text");
newElapsedTime.innerText = "00:00:00";
newDiv.appendChild(newElapsedTime);
mainDiv.appendChild(newDiv);
let elapsedSeconds = 0;
updateTimerValue()
function updateTimerValue() {
// Count the seconds, minutes and hours, convert to strings and pad the strings with leading 0's
const seconds = String(elapsedSeconds % 60).padStart(2, '0')
const minutes = String(parseInt(elapsedSeconds / 60 % 60)).padStart(2, '0')
const hours = String(parseInt(elapsedSeconds / 60 / 60)).padStart(2, '0')
// Use string interpolation for formatting the timer string
newElapsedTime.innerText = `${hours}:${minutes}:${seconds}`
}
}
}
button {
text-align: center;
min-width: 8rem;
}
.fa-play:after {
content: "[play]"
}
<section class="option-sec">
<div class = "container">
<div class = "row">
<div class ="col col-md-2">
<button class = "opt-btn" value="BATHROOM"><img src ="/images/bathroom.png" class="option"></button>
</div>
<div class ="col col-md-2">
<button class = "opt-btn" value="COMMUTE"><img src ="/images/commute.png" class="option"></button>
</div>
<div class ="col col-md-2">
<button class = "opt-btn" value="WORK"><img src ="/images/desk.png" class="option"></button>
</div>
<div class ="col col-md-2">
<button class = "opt-btn" value="ENT"><img src ="/images/ent.png" class="option"></button>
</div>
<div class ="col col-md-2">
<button class = "opt-btn" value="FAMILY"><img src ="/images/family.png" class="option"></button>
</div>
<div class ="col col-md-2">
<button class = "opt-btn" value="FITNESS"><img src ="/images/gym.png" class="option"></button>
</div>
<div class ="col col-md-2">
<button class = "opt-btn" value="MEAL"><img src ="/images/meal.png" class="option"></button>
</div>
<div class ="col col-md-2">
<button class = "opt-btn" value="MEDITATE"><img src ="/images/meditate.png" class="option"></button>
</div>
<div class ="col col-md-2">
<button class = "opt-btn" value="RELIGION"><img src ="/images/pray.png" class="option"></button>
</div>
<div class ="col col-md-2">
<button class = "opt-btn" value="SHOPPING"><img src ="/images/shopping.png" class="option"></button>
</div>
<div class ="col col-md-2">
<button class = "opt-btn" value="SLEEP"><img src ="/images/sleep.png" class="option"></button>
</div>
<div class ="col col-md-2">
<button class = "opt-btn" value="STUDY"><img src ="/images/study.png" class="option"></button>
</div>
</div>
</div>
</section>
<!--End Of Option Buttons-->
<section class="date-area">
<h1 class="date-hdr">CURRENT DATE: </h1>
<h2 class="date-hdr" id="insert-date"></h2>
</section>
<div class ="activity-container">
<div id="activities" class="activity-list"></div>
</div>
I thoroughly recommend writing code in strict mode and resolving any errors generated by the compiler because of it.

The issue likely is with this line:
activityInterval = setInterval(() => {
elapsedSeconds++
updateTimerValue()
}, 1000)
})
Each time you call addActivity(), you end up redefining activityInterval to the interval specific to the most recent activity created. Therefore, in stopTimer(), you only stop the most recent timer.
Instead, activityInterval needs to be a local variable. Delcare it outside the event handler first, and write a arrow function callback in stopTimer():
local declaration:
let activityInterval;
newPlay.addEventListener("click", () => {
activityInterval = setInterval(() => {
elapsedSeconds++;
updateTimerValue();
}, 1000);
callback:
newStop.addEventListener("click", () => clearInterval(activityInterval));
Here's a codesandbox too: https://codesandbox.io/s/zealous-jepsen-7gc4fn?file=/src/index.js:1100-1286
(I didn't have the images, and "date" is just a placeholder)

Related

How to create elements and delete them with native javascript without using jQuery

In the below link, there is a add more button, i want the add more to create the same input field with a delete button associated with it, but i would like to do it all with native js if possible.
https://codepen.io/aazim-khaki/pen/vYZmMRq
Current JS :
$(function() {
$(".btn-copy").on('click', function() {
var ele = $(this).closest('.example-2').clone(true);
ele.find('input').val('')
if (ele.find('button').length < 2) {
let btn = document.createElement("button");
btn.innerHTML = "Delete";
btn.onclick = (e) => {
e.preventDefault()
ele.remove()
}
ele[0].appendChild(btn);
}
$(this).closest('.example-2').after(ele);
})
})
Delegate
I moved the form tag and gave the button a delete class
window.addEventListener("load", function() {
document.querySelector(".row").addEventListener("click", function(e) {
const tgt = e.target;
if (tgt.classList.contains('delete')) {
tgt.closest('.example-2').remove()
} else if (tgt.classList.contains('btn-copy')) {
const ele = tgt.closest(".example-2").cloneNode(true);
ele.querySelector("input").value = "";
if (ele.querySelectorAll("button").length < 2) {
let btn = document.createElement("button");
btn.innerHTML = "Delete";
btn.classList.add("delete");
ele.appendChild(btn);
}
tgt.closest(".card-body").appendChild(ele)
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="row">
<div class="col-lg-12">
<div class="card">
<div class="card-header">
<h5 class="card-title">Add Class</h5>
</div>
<form action="#">
<div class="card-body">
<div class="example-2 form-group row">
<!--<label class="col-form-label col-md-2">Input Addons</label>-->
<div class="col-xs-2">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">Class Name</span>
</div>
<input class="form-control" type="text">
<div class="input-group-append">
<button class="btn-copy btn btn-primary" type="button">Add More</button>
</div>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-xs-2">
<button class="btn btn-primary" type="button">Submit</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
Use a container and event delegation so you only use one listener rather than attaching a listener to each remove button.
This is a very simple example but the principles are the same.
// Cache your elements
const container = document.querySelector('#container');
const add = document.querySelector('button');
// Add your container an add button listeners
container.addEventListener('click', handleEvent, false);
add.addEventListener('click', handleAdd(), false);
// If a remove button is clicked, find the
// the closest div wrapper and remove it from
// the container
function handleEvent(e) {
const { id } = e.target.dataset;
const row = e.target.closest('.row');
container.removeChild(row);
}
// `handleAdd` returns a function (closure) that
// is used for the add listener rather than
// maintaining a global variable.
// We initialise the id at this point
function handleAdd(id = 0) {
// And now return the function that will be called
// when the add button is clicked
// For the purposes of this example it simply adds new HTML
// to the container, and then increases the id
return function() {
const html = `<div class="row"><input value="${id}" /><button data-id="${id}">Remove</button></div>`;
container.insertAdjacentHTML('beforeend', html);
++id;
}
}
<button>Add</button>
<div id="container"></div>

How to change the id of an element in html using javascript?

It is my first time using JavaScript. I am trying to make a button where every time visitors click, it'll show another extra line of text. I often get an error on my JavaScript, and I'm not sure how to fix it. Thank you so much!
HTML;
<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
<div class="container">
<div class="text">
<div class="one hide">
One
</div>
<div class="two hide">
Two
</div>
<div class="three hide">
Three
</div>
</div>
</div>
</body>
<script src="script.js"></script>
</html>
JS;
const text = document.querySelector('.text');
const hide = document.querySelector('.hide');
const one = document.querySelector('.one');
const two = document.querySelector('.two');
var hr1 = document.getElementById('hr1');
var hr2 = document.getELementById('hr2');
var hr3 = document.getElementById('hr3');
hr1.addEventListener('click', () => {
one.classList.remove('hide');
hr1.id = "hr2";
})
// I often get an error on hr2.addEventListener
hr2.addEventListener('click', () => {
two.classList.remove('hide');
hr2.id = "hr3";
})
Your code throws error because you are trying to set hr2 and hr3 when they are not exist.
You need to set hr2 and hr3 variables after setting id's of them like below:
hr1.id = "hr2";
hr2= document.getElementById('hr2');
const text = document.querySelector('.text');
const hide = document.querySelector('.hide');
const one = document.querySelector('.one');
const two = document.querySelector('.two');
var hr1 = document.getElementById('hr1');
var hr2 = null;
var hr3 = null;
hr1.addEventListener('click', () => {
//one.classList.remove('hide');
hr1.id = "hr2";
hr2= document.getElementById('hr2');
console.log(hr2);
hr2.addEventListener('click', () => {
two.classList.remove('hide');
hr2.id = "hr3";
hr3 = document.getElementById('hr3');
console.log(hr3);
})
})
// I often get an error on hr2.addEventListener
<div class="container">
<div class="text">
<div class="one hide">
One
</div>
<div class="two hide">
Two
</div>
<div class="three hide">
Three
</div>
</div>
clickme
</div>

How create multiple user control on one page?

I have class Player which creates audio player, in static method create of this class i render ui of player an insert to dom using innerHtml, with one instance on page it's works fine, but i want creaete many instances on one page. How can I do it? Maybe we have any pattern to do that?
class Player1{
player = new Audio()
timeMouseDown = false
volumeMouseDown = false
isRateListActive = false
isVolumeControlActive = false
btnPlay = document.querySelector('.play')
btnVolume = document.querySelector('.volume')
btnDownload = document.querySelector('.download')
btnUpload = document.querySelector('.upload')
// another elements
constructor(){
this.player.src = "audio/1.mp3"
this.player.loop = true
}
initListeners(){
this.btnPlay.addEventListener('click', this.play.bind(this))
this.btnVolume.addEventListener('click',this.showVolumeControl.bind(this))
this.btnDownload.addEventListener('click',this.download.bind(this))
this.btnUpload.addEventListener('click',this.upload.bind(this))
//another events
}
static create(root){
debugger
this.root = document.querySelector(root);
this.root.innerHTML = `<div class="player">
<div class="title" oncopy="return false">Название дорожки</div>
<div class="controls">
<i class="fa fa-play"></i>
<span class="time past_time">00:00</span>
<div class="progress">
<div class="progress_bar" data-progress="6rem">
<div class="progress_line"></div>
<div class="progress_control"></div>
</div>
</div>
<span class="time rest_time">00:00</span>
<div class="rate_picker">
<div class="rate_list">
//some tags
</div>
</div>
</div>`
}
initPlayer() {
this.initListeners()
}
}

how to make the button click executes code only one time

I want the button with the id #show-text-area execute the postButton(); function only once so it won't create a second elements whenever clicked (i want it to create it for only one time and won't work again until clicked another button).
Hope my question was clear enough.
HTML
<div id="post-creator" class="creator-container">
<div class="post-type">
<div class="text-post" id="post">
<button onclick="postButton();">Post</button>
</div>
<div class="media-post">Image & Video</div>
<div class="link-post">Link</div>
</div>
<div class="post-title">
<input type="text" class="title-text" name="post-title" placeholder="Title">
</div>
<div class="post-content">
</div>
<div class="post-footer">
<div class="spoiler">Spoiler</div>
<div class="nsfw">NSFW</div>
<button class="post">post</button>
</div>
</div>
Javascript
let postButton = function() {
let textarea = document.createElement('textarea');
textarea.setAttribute('class', 'post-data');
textarea.setAttribute('placeholder', 'Text (optional)');
document.querySelector('.post-content').appendChild(textarea);
}
You could disable the button after activation, this has the benefit of informing the user that further clicks won't do anything.
let postButton = function() {
let textarea = document.createElement('textarea');
textarea.setAttribute('class', 'post-data');
textarea.setAttribute('placeholder', 'Text (optional)');
document.querySelector('.post-content').appendChild(textarea);
document.getElementsByTagName("button")[0].disabled = true;
}
Otherwise you could simply have the function short-circuit if it has already been called.
// alreadyPosted is scoped outside of the function so it will retain its value
// across calls to postButton()
let alreadyPosted = false;
let postButton = function() {
// do nothing if this isn't the first call
if (alreadyPosted) { return; }
// mark the function as called
alreadyPosted = true;
let textarea = document.createElement('textarea');
textarea.setAttribute('class', 'post-data');
textarea.setAttribute('placeholder', 'Text (optional)');
document.querySelector('.post-content').appendChild(textarea);
document.getElementsByTagName("button")[0].disabled = true;
}
The following works.
let postButton = function(event) {
event.target.disabled = true;
let textarea = document.createElement('textarea');
textarea.setAttribute('class', 'post-data');
textarea.setAttribute('placeholder', 'Text (optional)');
document.querySelector('.post-content').appendChild(textarea);
};
document.getElementById('post').addEventListener('click', postButton);
<div id="post-creator" class="creator-container">
<div class="post-type">
<div class="text-post" id="post">
<button>Post</button>
</div>
<div class="media-post">Image & Video</div>
<div class="link-post">Link</div>
</div>
<div class="post-title">
<input type="text" class="title-text" name="post-title" placeholder="Title">
</div>
<div class="post-content">
</div>
<div class="post-footer">
<div class="spoiler">Spoiler</div>
<div class="nsfw">NSFW</div>
<button class="post">post</button>
</div>
</div>
You can also use hide show function on textarea if you do not want to create one.
let postButton = function() {
let d = document.getElementById('post_data').style.display;
if(d=='none'){
document.getElementById('post_data').style.display = 'block';
}
}
document.getElementById('post_data').style.display = 'none';
document.getElementById('post_btn').addEventListener('click', postButton);
<div id="post-creator" class="creator-container">
<div class="post-type">
<div class="text-post">
<button id="post_btn">Post</button>
</div>
<div class="media-post">Image & Video</div>
<div class="link-post">Link</div>
</div>
<div class="post-title">
<input type="text" class="title-text" name="post-title" placeholder="Title">
</div>
<div class="post-content">
<textarea class="post-data" id="post_data" placeholder="Text (optional)"></textarea>
</div>
<div class="post-footer">
<div class="spoiler">Spoiler</div>
<div class="nsfw">NSFW</div>
<button class="post">post</button>
</div>
</div>

how to create generic html with javascript

I have the following html:
<div id="prog" class="downloads clearfix">
<div class="item">
<div class="image_container">
<img src="/img/downloads/company.png" width="168" height="238" alt="">
</div>
<div class="title">
pricelist: <label id="pr1"></label>
</div>
<div class="type">
pdf document
</div>
<div class="link">
<a id="pdfdocument" class="button" target="_blank" href="#">start Download </a>
</div>
</div>
</div>
I want build HTML which is inside the <div id="prog"> with Javascript:
<div id="prog" class="downloads clearfix"></div>
I'm trying to use this Javascript, but without success:
var tmpDocument, tmpAnchorTagPdf, tmpAnchorTagXls, parentContainer, i;
parentContainer = document.getElementById('prog');
for (i = 0; i < documents.length; i++) {
tmpDocument = documents[i];
tmpAnchorTagPdf = document.createElement('a id="pdfdocument" ');
tmpAnchorTagPdf.href = '/role?element=' + contentElement.id + '&handle=' + ope.handle;
tmpAnchorTagPdf.innerHTML = 'start Download';
tmpAnchorTagXls = document.createElement('a');
tmpAnchorTagXls.href = '/role?element=' + contentElement.id + '&handle=' + ope.handle;
tmpAnchorTagXls.innerHTML = 'start Download';
parentContainer.appendChild(tmpAnchorTagPdf);
parentContainer.appendChild(tmpAnchorTagXls);
}
If this is a section of code that you will be using more than once, you could take the following approach.
Here is the original div without the code you want to create:
<div id="prog" class="downloads clearfix">
</div>
Create a template in a hidden div like:
<div id="itemtemplate" style="display: none;">
<div class="item">
<div class="image_container">
<img src="/img/downloads/company.png" width="168" height="238" alt="">
</div>
<div class="title">
pricelist: <label></label>
</div>
<div class="type">
pdf document
</div>
<div class="link">
<a class="button" target="_blank" href="#">start Download </a>
</div>
</div>
</div>
Then duplicate it with jquery (OP originally had a jquery tag; see below for JS), update some HTML in the duplicated div, then add it to the document
function addItem() {
var item = $("#itemtemplate div.item").clone();
//then you can search inside the item
//let's set the id of the "a" back to what it was in your example
item.find("div.link a").attr("id", "pdfdocument");
//...the id of the label
item.find("div.title label").attr("id", "pr1");
//then add the objects to the #prog div
$("#prog").append(item);
}
update
Here is the same addItem() function for this example using pure Javascript:
function JSaddItem() {
//get the template
var template = document.getElementById("itemtemplate");
//get the starting item
var tempitem = template.firstChild;
while(tempitem != null && tempitem.nodeName != "DIV") {
tempitem = tempitem.nextSibling;
}
if (tempitem == null) return;
//clone the item
var item = tempitem.cloneNode(true);
//update the id of the link
var a = item.querySelector(".link > a");
a.id = "pdfdocument";
//update the id of the label
var l = item.querySelector(".title > label");
l.id = "pr1";
//get the prog div
var prog = document.getElementById("prog");
//append the new div
prog.appendChild(item);
}
I put together a JSFiddle with both approaches here.

Categories