I am trying to show the countdown timer function in the browser display of item card element. I have created the array of objects with key value pairs in it. I have count down timer function included in the array as well, I have managed to show the timer in one item card (picture attached) but struggling to find out how can I show that timer function in all item cards. codes are here:--
let products = {
data: [
{
productName: "Orange",
catagory: "Fruits",
price: "5 / kg",
image: "orange1.jpg",
desc: "Freshly Picked Oranges",
timer: setInterval('updateTimer()', 1000)
},
{
productName: "Apple",
catagory: "Fruits",
price: "4 / kg",
image: "apple.jpg",
desc: "Freshly Picked Apple",
timer: setInterval('updateTimer()', 1000)
},
{
productName: "Pear",
catagory: "Fruits",
price: "6 / kg",
image: "pear1.jpg",
desc: "Freshly Picked Pear",
},
{
productName: "Banana",
catagory: "Fruits",
price: "4 / kg",
image: "banana1.jpg",
desc: "Freshly Picked Banana",
}
]
};
for (let i of products.data) {
let card = document.createElement("div");
card.classList.add("card", i.catagory, "hide");
let imgContainer = document.createElement("div");
imgContainer.classList.add("image-container");
let image = document.createElement("img");
image.setAttribute("src", i.image);
imgContainer.appendChild(image);
card.appendChild(imgContainer);
let container = document.createElement("div");
container.classList.add("container");
let name = document.createElement("h4");
name.classList.add("product-name");
name.innerText = i.productName.toUpperCase();
container.appendChild(name);
let desc = document.createElement("h5");
desc.innerText = i.desc;
container.appendChild(desc);
let price = document.createElement("h4");
price.innerText ="£" i.price;
container.appendChild(price);
let timer = document.createElement("h3");
timer.setAttribute("id", "timer");
container.appendChild(timer);
card.appendChild(container);
document.getElementById("products").appendChild(card);
}
function updateTimer() {
future = Date.parse("apr 28, 2022 15:05:00");
now = new Date();
diff = future - now;
days = Math.floor(diff / (1000 * 60 * 60 * 24));
hours = Math.floor(diff / (1000 * 60 * 60));
mins = Math.floor(diff / (1000 * 60));
secs = Math.floor(diff / 1000);
d = days;
h = hours - days * 24;
m = mins - hours * 60;
s = secs - mins * 60;
document.getElementById("timer")
.innerHTML =
'<div>' d '<span>Days</span></div>'
'<div>' h '<span>Hrs</span></div>'
'<div>' m '<span>Mins</span></div>'
'<div>' s '<span>Secs</span></div>';
if (diff < 0) {
clearInterval(setInterval('updateTimer()', 1000));
document.getElementById("timer")
innerHTML = `<h4 style="color:red" >sorry, this item has expired!</h4>`;
}
}
<body>
<div >
<div id="search-container">
<input type="search" name="" id="search-input" placeholder="Search...">
<button id="search">Search</button>
</div>
<div id="buttons">
<button onclick="filterProduct('all')">All</button>
<button onclick="filterProduct('Fruits')">Fruits</button>
<button onclick="filterProduct('Vegetable')">Vegetable</button>
<button onclick="filterProduct('Dairy')">Dairy</button>
<button onclick="filterProduct('Bakery')">Bakery</button>
</div>
<div id="products"></div>
<!-- <div id="timer">0</div> -->
</div>
<script src="script.js"></script>
</body>
CodePudding user response:
document.getElementById("timer")
only returns the first timer it finds. Which, since ids should be unique, should be OK, but you want to create multiple of these, so you should probably use classes which can be repeated. As-is you cannot use multiple ids and this getElementByID()
method.
So, if you have
<div data-expiration="123456789">10 minutes</div>
Or something like that, you can then iterate over all timers with something like.
document.querySelectorAll('.timer').forEach(timer => {
// update time.
});
Or, you can attach the timer to the individual elements in DOM on each timeout. This would be more of a component driven pattern for example with React / etc. The component would handle it's own state and you'd set the expiration / etc on each instance of a count-down and then you can have each instance manage its own state / presentation.
CodePudding user response:
The problem is that you only have one instance of Timer
but you need multiple independent instances as different products may have different offers. Your modifying your one instance from multiple items. You could use classes for this kind of thing. Here a class Timer
which implements what you want.
It takes an endDate
and and HTMLElement
which it is attached to and then shows a timer. Every time you need a timer you then just have to create an HTML element for it and a new instance of Timer
.
class Timer {
#end;
#attached;
#interval;
/**
*
* @param {string} endTime Dates string (or other values that can be parsed to Date() constructor)
* @param {HTMLElement} attachTo HTML element this timer is attached to
*/
constructor(endTime, attachTo) {
this.#end = new Date(endTime);
this.#attached = attachTo;
}
start() {
// start timer interval
this.#interval = setInterval(() => this.updateTimer(), 1000);
}
stop() {
// stop the timer
clearInterval(this.#interval);
}
updateTimer() {
const diff = this.#end - new Date();
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
const hours = Math.floor(diff / (1000 * 60 * 60));
const mins = Math.floor(diff / (1000 * 60));
const secs = Math.floor(diff / 1000);
const d = days;
const h = hours - days * 24;
const m = mins - hours * 60;
const s = secs - mins * 60;
if(this.#attached){
this.#attached.innerHTML =
"<div>"
d
"<span>Days</span></div>"
"<div>"
h
"<span>Hrs</span></div>"
"<div>"
m
"<span>Mins</span></div>"
"<div>"
s
"<span>Secs</span></div>";
if (diff < 0) {
this.stop();
this.#attached.innerHTML = `<h4 style="color:red" >sorry, this item has expired!</h4>`;
}
}
}
}
let products = {
data: [
{
productName: "Orange",
catagory: "Fruits",
price: "5 / kg",
image: "orange1.jpg",
desc: "Freshly Picked Oranges",
offerUntil: new Date(2022, 3, 28).toISOString()
},
{
productName: "Apple",
catagory: "Fruits",
price: "4 / kg",
image: "apple.jpg",
desc: "Freshly Picked Apple",
offerUntil: new Date(2022, 3, 25).toISOString()
},
{
productName: "Pear",
catagory: "Fruits",
price: "6 / kg",
image: "pear1.jpg",
desc: "Freshly Picked Pear",
},
{
productName: "Banana",
catagory: "Fruits",
price: "4 / kg",
image: "banana1.jpg",
desc: "Freshly Picked Banana",
},
],
};
// wait until HTML document is loadedn
window.addEventListener("DOMContentLoaded", e => {
for (let i of products.data) {
let card = document.createElement("div");
card.classList.add("card", i.catagory, "hide");
let imgContainer = document.createElement("div");
imgContainer.classList.add("image-container");
let image = document.createElement("img");
image.setAttribute("src", i.image);
imgContainer.appendChild(image);
card.appendChild(imgContainer);
let container = document.createElement("div");
container.classList.add("container");
let name = document.createElement("h4");
name.classList.add("product-name");
name.innerText = i.productName.toUpperCase();
container.appendChild(name);
let desc = document.createElement("h5");
desc.innerText = i.desc;
container.appendChild(desc);
let price = document.createElement("h4");
price.innerText = "£" i.price;
container.appendChild(price);
// if this is an limited offer, attach a timer
if(i.offerUntil){
let timer = document.createElement("h3");
// create a new timer for given date and attach it to the newly created h3
i.timer = new Timer(i.offerUntil, timer);
i.timer.start();
container.appendChild(timer);
}
card.appendChild(container);
document.getElementById("products").appendChild(card);
}
})
<body>
<div >
<div id="search-container">
<input type="search" name="" id="search-input" placeholder="Search...">
<button id="search">Search</button>
</div>
<div id="buttons">
<button onclick="filterProduct('all')">All</button>
<button onclick="filterProduct('Fruits')">Fruits</button>
<button onclick="filterProduct('Vegetable')">Vegetable</button>
<button onclick="filterProduct('Dairy')">Dairy</button>
<button onclick="filterProduct('Bakery')">Bakery</button>
</div>
<div id="products"></div>
<!-- <div id="timer">0</div> -->
</div>
<script src="script.js"></script>
</body>
You should wait until the HTML document is loaded before attaching event handlers and alike using the DOMContentLoaded
event.
You could also improve your code by not using innerHTML
and only do the calculation of time difference once. You could then store the difference in days, hours etc. as a class attribute and just decrement that, which will be less calculations.
Please note: I have adjusted your input so each product can have a Date as an ISO-8601 string when the offer is no longer available.