How create multiple user control on one page? - javascript

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()
}
}

Related

setInterval for multiple elments (Vanilla 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)

Getting attribute from element

I made an object to manage the candidate education in online marketplace
let education ={
tabs:{},
render:function(){
document.querySelector('#education-tabs').innerHTML = ''
let container =``;
Object.values(this.tabs).map((tab)=>{
container += `
<div class="education-tab-container">
<div class="education-tab-right">
<div class="big-title">
${tab.faculty}
</div>
<div class="medium-title">
${tab.department}
</div>
<div class="small-title">
${tab.university}
</div>
</div>
<div class="education-tab-left">
<div class="date">
${tab.from || ''} ${tab.to || ''}
</div>
<div class="close" data-id="${tab.id}">
<span>x</span>
</div>
</div>
</div>
`
})
document.querySelector('#education-tabs').innerHTML = container
},
deleteTab:function(id){
delete this.tabs[id];
this.render();
},
add:async function(payload){
this.tabs = {...this.tabs,[payload.id]:payload}
this.render();
}
}
This updates the UI every time I add a new tab to this object.
The problem is when I try to remove a tab: sometimes I get the data-id attribute of the tab that I made and sometimes it returns null.
here is my function
const triggerListener = ()=>{
$("#education-tabs").find(".education-tab-left .close").click((e)=>{
if(e.target.getAttribute('data-id')){
alert(e.target.getAttribute('data-id'))
}
});
so is there a way to solve this problem?

How to click on a child element

There many items and I get object of the desired item. But I don't know how can I click on the child element in this object.
html:
<div class="item">
<div role="button" tabindex="-1">
<strong>ItemName2</strong>
</div>
<div class="d">
<div class="item-icon" role="button" tabindex="-1" style="display: none">
<i aria-label="icon: add" class="add"></i> <!-- I need to click on this Item -->
</div>
<div class="item-icon" role="button" tabindex="-1" style="display: none">
<i aria-label="icon: del" class="del"></i>
</div>
</div>
</div>
<div class="item"> ... </div>
<div class="item"> ... </div>
<div class="item"> ... </div>
js:
let fBtns = await driver.findElements(By.tagName('strong')); // Find all objects
let buttons = fBtns.map(elem => elem.getText());
const allButtons = await Promise.all(buttons);
console.log(allButtons); // All object names
let current = fBtns[fBtns.length - 1];
console.log(current); // This is desired object
await current.click(); // This is click on the object and operates as expected
// But I need to click on the <i aria-label="icon: add" class="add"> element
// How can I click on the desired element?
To click the element <i aria-label="icon: del" class="del"></i>, you can just use an XPath to query directly on the element:
await driver.findElement(By.xpath("//div[div/strong[text()='ItemName2']]/div/div/i[#class='del']")).click()
You can probably shorten this a bit to:
await driver.findElement(By.xpath("//div[div/strong[text()='ItemName2']]//i[#class='del']")).click()
Try invoking click by trigger method:
$('.item-icon .add').trigger("click");
In the below example, I scan the document for a dynamic xpath that finds the strong with string ItemName2 and then traverse back up one level (/../) before moving back down to the child element. This will act like a waitForElement that you can hopefully repurpose to trigger a click.
var MyDefaultTimeout = 1500;
var loaded = false;
do {
var icon = document.getElementsByClassName('//*[contains(#strong,\'ItemName2\')]/../div/div/i');
if(!icon.length == 0)
{
setTimeout(function() { loaded = false }, MyDefaultTimeout);
}
else
{
if(!document.readyState === 'complete')
{
setTimeout(function() { loaded = false }, MyDefaultTimeout);
}
else
{
loaded = true;
return document.readyState;
}
}
}
while(loaded === false);

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 iterate through an API response and insert proper data to identical divs?

I’m trying to create a „weather app” which is supposed to look like this: https://pictr.com/image/05IPpQ
I created an html markup, which looks like this:
<div class="card">
<div class="card__box card__box--blue" draggable="true" id="box1">
<div class="card__box--icon"></div>
<div class="card__box--city"></div>
<div class="card__box--country"></div>
<div class="card__box--weather"></div>
<div class="card__box--temperature"></div>
<div class="card__box--close">
<i class="fa fa-window-close" aria-hidden="true"></i>
</div>
</div>
<div class="card__box card__box--orange" draggable="true" id="box2">
<div class="card__box--icon"></div>
<div class="card__box--city"></div>
<div class="card__box--country"></div>
<div class="card__box--weather"></div>
<div class="card__box--temperature"></div>
<div class="card__box--close">
<i class="fa fa-window-close" aria-hidden="true"></i>
</div>
</div>
<div class="card__box card__box--grey" draggable="true" id="box3">
<div class="card__box--icon"></div>
<div class="card__box--city"></div>
<div class="card__box--country"></div>
<div class="card__box--weather"></div>
<div class="card__box--temperature"></div>
<div class="card__box--close">
<i class="fa fa-window-close" aria-hidden="true"></i>
</div>
</div>
<div class="card__box card__box--new" draggable="false" id="empty">
<div class="card__box--add">
<i class="fa fa-plus" aria-hidden="true"></i>
</div>
</div>
<p class="card__paragraph paragraph--smallest paragraph--grey text-center">Add more widgets</p>
</div>
I’m trying to make this actually work by the use of https://openweathermap.org.
This is an example of the APIs’ response which I’m getting:
https://samples.openweathermap.org/data/2.5/group?id=524901,703448,2643743&units=metric&appid=b6907d289e10d714a6e88b30761fae22
My problem is that I don’t know how to iterate through the response and fill all of my ‚boxes’ with proper data from the API. I want the first object from the list to fill the first box, etc. Here’s my code.
document.addEventListener('DOMContentLoaded', getWeather);
// These are hard coded ID's of the cities (only way to get data for multiple cities without a premium account)
const locations = ['524901', '703448', '2643743'];
const weather = new Weather();
function getWeather() {
const ui = new UI();
weather
.getWeather()
.then(response => ui.display(response.list))
.catch(err => console.log(err));
}
class Weather {
constructor(locations) {
this.apiKey = 'b5f8df2f8d10af0fda257b295738dcdd';
this.locations = locations;
}
// Fetch data from API
async getWeather() {
const response = await fetch(
`http://api.openweathermap.org/data/2.5/group?id=${locations}&units=metric&appid=${
this.apiKey
}`
);
const responseData = await response.json();
return responseData;
}
}
The display function is currently hardcoded to get the first object from the list, which is something i’m trying to avoid, and is currently left as is, just so that the app ‚works’.
class UI {
constructor() {
this.city = document.querySelector('.card__box--city');
this.country = document.querySelector('.card__box--country');
this.weather = document.querySelector('.card__box--weather');
this.temperature = document.querySelector('.card__box--temperature');
this.icon = document.querySelector('.card__box--icon');
}
display(response) {
this.city.textContent = response[0].name;
this.country.textContent = response[0].sys.country;
this.weather.textContent = response[0].weather[0].description;
this.temperature.textContent =
Math.ceil(response[0].main.temp) + String.fromCharCode(176);
const iconCode = response[0].weather[0].icon;
this.icon.style.backgroundImage = `url(http://openweathermap.org/img/w/${iconCode}.png)`;
}
}
I suspect there might be a problem with the UI constructor.

Categories