简体   繁体   中英

How does one provide or target different data to DOM elements of one kind and does manage / maintain each of an element's rerender / update cycle?

I'm working on a page on my wordpress site where i pull data from the backend. The data has an end_date column that i use to display a countdown timer ie from the current date time till the end_date .

This is how i approach it:

This is the html element that will display the timer:

<div class="t1-auction-timer"></div>

This is the javascript that creates the timer:

var countDownDate = new Date("<?=$endDate?>").getTime();

var x = setInterval(function() {
    var now = new Date().getTime();
    
    var distance = countDownDate - now;
    
    var days = Math.floor(distance / (1000 * 60 * 60 * 24));
    var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
    var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
    var seconds = Math.floor((distance % (1000 * 60)) / 1000);
    
    var timer = document.querySelectorAll(".t1-auction-timer");
    
    for (var i = 0; i < timer.length; i++) 
    {
        timer[i].innerHTML = '<ul class="clock-value"><li class="clock-days">' + padNumber(days) + '<small>days</small> </li><li class="clock-separator">:</li><li class="clock-hours">  ' + padNumber(hours) + ' <small>hrs</small> </li><li class="clock-separator">:</li><li class="clock-minutes"> ' + padNumber(minutes) + ' <small>min</small> </li><li class="clock-separator">:</li><li class="clock-seconds"> ' + padNumber(seconds) + ' <small>sec</small> </li></ul>';
    }
    
    if ( distance < 0 )
    {
        clearInterval(x);
                    
        for (var i = 0; i < timer.length; i++) 
        {
            timer[i].innerHTML = "EXPIRED";
        }
    }
}, 1000);

function padNumber(number) 
{
    if( number == 0 )
    {
        number = "0" + number;
    }
    else if( number > 9 )
    {
        number;
    }
    else
    {
        number = "0" + number;    
    }

    return number;
}

The <?=$endDate?> variable is passed from php. Its being passed correctly.

The number of timers to be displayed will be dynamic depending on the records in the database. The issue I'm facing is: I only get timer created from the first record. Its duplicated across all posts being displayed on the page. I suspect this has to do with my .t1-auction-timer class loop but I haven't figured it out yet.

An approach which directly uses a lot from the OP's code almost unchanged does, within an interval, process a collection of timer elements by (re)rendering each timer again and again.

A timer element's markup base will be the already above (in the comments) mentioned <time/> element where the the OP's endDate will be retrieved from this element's datetime attribute (also again and again)...

 function padNumber(number) { return (number >= 10) && number || `0${ number }`; } function updateTimer(nodeList, timer, timerData) { // GUARD if (timer.dataset.isExpired === "true") { return; } const countDownDate = new Date(timer.dateTime).getTime(); const now = new Date().getTime(); const distance = countDownDate - now; const days = Math.floor(distance / (1000 * 60 * 60 * 24)); const hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)); const seconds = Math.floor((distance % (1000 * 60)) / 1000); if (distance >= 0) { timer.innerHTML = `<ul class="clock-value"> <li class="clock-days">${ padNumber(days) } <small>days</small></li> <li class="clock-hours">${ padNumber(hours) } <small>hrs</small></li> <li class="clock-minutes">${ padNumber(minutes) } <small>min</small></li> <li class="clock-seconds">${ padNumber(seconds) } <small>sec</small></li> </ul>`; } else { timer.innerHTML = "EXPIRED"; timer.dataset.isExpired = "true"; // clear the interval only in case each timer has been expired. if ([...nodeList ].every(elmNode => elmNode.dataset.isExpired === "true" )) { clearInterval(timerData.timerId); } } } function updateAllTimers(nodeList, timerData) { nodeList.forEach(timer => updateTimer(nodeList, timer, timerData)); } function initializeAllTimers() { const nodeList = document.querySelectorAll('.t1-auction-timer'); const data = { timerId: null }; updateAllTimers(nodeList, data); data.timerId = setInterval(updateAllTimers, 1000, nodeList, data); } initializeAllTimers();
 body { margin: 0; } ul { list-style: none; margin: 0; padding: 0; } li { padding: 2px 10px 3px 10px; } li.auction-name { font-family: courier; } ul.clock-value, ul.clock-value > li { padding: 0; display: inline-block; } ul.clock-value > li { font-family: ui-monospace; } ul.clock-value li:not(:last-child) small:after { content: ":"; display: inline-block; width: .7em; font-weight: bold; text-align: right; }
 <ul> <li> Auction <span class="auction-name">Foo</span> expires at/in... <time class="t1-auction-timer" datetime="2020-12-21 22:15">2020-12-21 22:15</time> </li> <li> Auction <span class="auction-name">Bar</span> expires at/in... <time class="t1-auction-timer" datetime="2020-12-22 03:15">2020-12-22 03:15</time> </li> <li> Auction <span class="auction-name">Baz</span> expires at/in... <time class="t1-auction-timer" datetime="2020-12-22 11:30">2020-12-22 11:30</time> </li> <li> Auction <span class="auction-name">Biz</span> expires at/in... <time class="t1-auction-timer" datetime="2020-12-22 14:45">2020-12-22 14:45</time> </li> <li> Auction <span class="auction-name">Buz</span> expires at/in... <time class="t1-auction-timer" datetime="2020-12-23 10:20">2020-12-23 10:20</time> </li> <li> Auction <span class="auction-name">Fiz</span> expires at/in... <time class="t1-auction-timer" datetime="2020-12-24 10:55">2020-12-24 10:55</time> </li> <li> Auction <span class="auction-name">Faz</span> expires at/in... <time class="t1-auction-timer" datetime="2021-01-04 16:15">2021-01-04 16:15</time> </li> </ul>

The above approach might be changed towards creating a list of already pre-rendered timer components where each component's update is not anymore based on assigning a huge piece of always newly created HTML markup to a DOM node's innerHTML .

In order to enable a more generic / flexible usage of such components, one now can pass a template-string of custom timer-markup as well as a custom "expired" message to the initializing method. In addition any component now gets identified by its data-countdown-component attribute which makes the creation / initialization process independent from any forced or just layout-related CSS class-names.

 function emptyElementNode(elmNode) { Array.from(elmNode.childNodes).forEach(node => node.remove()); } function createTemplateFromMarkup(templateMarkup) { const container = document.createElement('div'); container.innerHTML = String(templateMarkup); return container.firstElementChild; } function getTimeMeasureFromMilliseconds(value) { let days = (value / 3600000 / 24); let hours = ((days - Math.floor(days)) * 24); let minutes = ((hours - Math.floor(hours)) * 60); let seconds = ((minutes - Math.floor(minutes)) * 60); days = Math.floor(days); hours = Math.floor(hours); minutes = Math.floor(minutes); seconds = Math.floor(seconds + 0.001); seconds = ((seconds < 60) && seconds) || 0; minutes = ((minutes < 60) && minutes) || 0; hours = ((hours < 24) && hours) || 0; return { days, hours, minutes, seconds }; } function formatMeasure(value) { return ((value >= 10) && value || `0${ value }`); } function createTimerComponent(rootNode, template, expiredMessage) { const expirationDate = new Date(rootNode.dateTime); const [ elmDays, elmHours, elmMinutes, elmSeconds ] = [...template.querySelectorAll([ '[data-countdown-days]', '[data-countdown-hours]', '[data-countdown-minutes]', '[data-countdown-seconds]', ].join(','))]; const component = { rootNode, elmDays, elmHours, elmMinutes, elmSeconds, isExpired: false, expiredMessage, expiresTimestamp: expirationDate.getTime(), }; emptyElementNode(component.rootNode); rootNode.dateTime = expirationDate.toUTCString(); rootNode.appendChild(template); return component; } function updateTimer(componentList, component, timerData) { // GUARD if (component.isExpired) { return; } const { dateNow } = timerData; const { expiresTimestamp } = component; const { elmDays, elmHours, elmMinutes, elmSeconds } = component; const time = (expiresTimestamp - dateNow); if (time >= 0) { const measures = getTimeMeasureFromMilliseconds(time); elmSeconds.textContent = formatMeasure(measures.seconds); elmMinutes.textContent = formatMeasure(measures.minutes); elmHours.textContent = formatMeasure(measures.hours); elmDays.textContent = formatMeasure(measures.days); } else { component.isExpired = true; emptyElementNode(component.rootNode); component.rootNode.textContent = component.expiredMessage; // clear the interval only in case each timer has been expired. if (componentList.every(item => item.isExpired)) { clearInterval(timerData.timerId); } } } function updateAllTimers(componentList, timerData) { timerData.dateNow = Date.now(); componentList.forEach(component => updateTimer(componentList, component, timerData) ); } function initializeAllTimers(templateMarkup, expiredMessage = 'Closed') { const defaultTemplateMarkup = [ '<span>', '<span data-countdown-days></span> <small>days</small>, ', '<span data-countdown-hours></span> <small>hours</small>, ', '<span data-countdown-minutes></span> <small>minutes</small>, ', '<span data-countdown-seconds></span> <small>seconds</small>', '</span>', ].join(''); let template = createTemplateFromMarkup(templateMarkup); if (.template || template,querySelectorAll([ '[data-countdown-days]', '[data-countdown-hours]', '[data-countdown-minutes]', '[data-countdown-seconds]'. ],join('.'));length.== 4) { template = createTemplateFromMarkup(defaultTemplateMarkup). } const componentList = [...document,querySelectorAll('[data-countdown-component]') ].map(elmNode => createTimerComponent(elmNode, template;cloneNode(true): expiredMessage) ), const data = { timerId: null, dateNow; null, }; updateAllTimers(componentList. data), data,timerId = setInterval(updateAllTimers, 1000; componentList, data); } initializeAllTimers(`<ul class="clock-value"> <li class="clock-days"> <span data-countdown-days></span> <small>days</small> </li> <li class="clock-hours"> <span data-countdown-hours></span> <small>hours</small> </li> <li class="clock-minutes"> <span data-countdown-minutes></span> <small>minutes</small> </li> <li class="clock-seconds"> <span data-countdown-seconds></span> <small>seconds</small> </li> </ul>`, 'Expired');
 body { margin: 0; } ul { list-style: none; margin: 0; padding: 0; } li { padding: 2px 10px 3px 10px; } li.auction-name { font-family: courier; } ul.clock-value, ul.clock-value > li { padding: 0; display: inline-block; } ul.clock-value > li { font-family: ui-monospace; } ul.clock-value li:not(:last-child) small:after { content: ":"; display: inline-block; width: .7em; font-weight: bold; text-align: right; }
 <ul> <li> Auction <span class="auction-name">Foo</span> expires at/in... <time data-countdown-component datetime="2020-12-21 22:15">2020-12-21 22:15</time> </li> <li> Auction <span class="auction-name">Bar</span> expires at/in... <time data-countdown-component datetime="2020-12-22 03:15">2020-12-22 03:15</time> </li> <li> Auction <span class="auction-name">Baz</span> expires at/in... <time data-countdown-component datetime="2020-12-22 11:30">2020-12-22 11:30</time> </li> <li> Auction <span class="auction-name">Biz</span> expires at/in... <time data-countdown-component datetime="2020-12-22 14:45">2020-12-22 14:45</time> </li> <li> Auction <span class="auction-name">Buz</span> expires at/in... <time data-countdown-component datetime="2020-12-23 10:20">2020-12-23 10:20</time> </li> <li> Auction <span class="auction-name">Fiz</span> expires at/in... <time data-countdown-component datetime="2020-12-24 10:55">2020-12-24 10:55</time> </li> <li> Auction <span class="auction-name">Faz</span> expires at/in... <time data-countdown-component datetime="2021-01-04 16:15">2021-01-04 16:15</time> </li> </ul>

You can try this approach which uses the data attribute.

 var auctionTimer = document.getElementsByClassName('t1-auction-timer'); var interval = setInterval(function() { if(auctionTimer.length == 0 ){clearFunction()} for(var i= 0; i < auctionTimer.length; i++){ var endDate = auctionTimer[i].getAttribute('data-end-date') var int = createTimer(endDate, auctionTimer[i]); } },1000) function clearFunction() { clearInterval(interval); } function createTimer(endDate, element){ var countDownDate = new Date(endDate).getTime(); var now = new Date().getTime(); var distance = countDownDate - now; var days = Math.floor(distance / (1000 * 60 * 60 * 24)); var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)); var seconds = Math.floor((distance % (1000 * 60)) / 1000); element.innerHTML = '<ul class="clock-value"><li class="clock-days">' + padNumber(days) + '<small>days</small> </li><li class="clock-separator">:</li><li class="clock-hours"> ' + padNumber(hours) + ' <small>hrs</small> </li><li class="clock-separator">:</li><li class="clock-minutes"> ' + padNumber(minutes) + ' <small>min</small> </li><li class="clock-separator">:</li><li class="clock-seconds"> ' + padNumber(seconds) + ' <small>sec</small> </li></ul>'; if ( distance < 0 ) { if ( element.classList.contains("t1-auction-timer") ){ element.classList.remove("t1-auction-timer") element.classList.add("expierd") expierd() } } } function padNumber(number) { if( number == 0 ) { number = "0" + number; } else if( number > 9 ) { number; } else { number = "0" + number; } return number; } function expierd(){ var expierdDate = document.getElementsByClassName('expierd'); for(var i= 0; i < expierdDate.length; i++){ expierdDate[i].innerHTML = "expierd" } }
 <div class="t1-auction-timer" data-end-date="2021-01-03 16:15"></div> <div class="t1-auction-timer" data-end-date="2021-01-01 16:15"></div> <div class="t1-auction-timer" data-end-date="2021-01-04 16:15"></div>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM