javascriptphpstopwatch

javaScript code working only in the first iteration of loop


I am trying to create stopwatch for multiple users with php and javascript, i am using mysql DB for users. When i click on on user the stopwatch will start but for others it is not working. I tried with loops also but not solved.

this is wep page screenshoot

Below is my code for PHP and JS.

JS

        let seconds = 00;
        let tens = 00;
        let mins = 00;
        let getSeconds = document.querySelector('.seconds');
        let getTens = document.querySelector('.tens');
        let getMins = document.querySelector('.mins');
        let btnStart = document.querySelector('.btn-start');
        // let btnStop = document.querySelector('.btn-stop');
        let btnReset = document.querySelector('.btn-reset');
        let interval;

        btnStart.addEventListener('click', () => {
            clearInterval(interval);
            interval = setInterval(startTimer, 10);
            console.log(interval);
        })
        // btnStop.addEventListener('click', () => {
        //     clearInterval(interval);
        // })
        btnReset.addEventListener('click', () => {
            clearInterval(interval);
            tens = '00';
            seconds = '00';
            mins = '00';
            getSeconds.innerHTML = seconds;
            getTens.innerHTML = tens;
            getMins.innerHTML = mins;
        })

        function startTimer(){
            tens++;
            if(tens <= 9){
                getTens.innerHTML = '0' + tens;
            }
            if(tens > 9){
                getTens.innerHTML = tens;
            }
            if(tens > 99){
                seconds++;
                getSeconds.innerHTML = '0' + seconds;
                tens = 0;
                getTens.innerHTML = '0' + 0;
            }
            if(seconds > 9){
                getSeconds.innerHTML = seconds;
            }
            if(seconds > 59){
                mins++;
                getMins.innerHTML = '0' + mins;
                seconds = 0;
                getSeconds.innerHTML = '0' + 0;
            }
            if(mins > 9){
                getSeconds.innerHTML = mins;
            }
        }

PHP

<?php
include("config.php");
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
</head>
<body>
    <div class="card">
        <div class="card-header">
            <h4> Patient Detailes </h4>
        </div>

        <div class="card-body ">
            <div class="table-responsive ">
                <table class="table table-bordered table-striped tablewatch">
                    <thead>
                        <tr>
                            <th>Id</th>
                            <th>Name</th>
                            <th>Age</th>
                            <th>Phone</th>
                            <th>Waiting Time</th>
                        </tr>
                    </thead>
                    <tbody>
                        <?php
                            $sql = "SELECT * FROM patient_detailes";
                            $query = mysqli_query($conn, $sql);
                            if($num = mysqli_num_rows($query)>0){
                                while($row = mysqli_fetch_array($query)){
                                    ?>
                        <tr>
                            <th scope="row"><?= $row['id'] ?></th>
                            <td><?= $row['name'] ?></td>
                            <td><?= $row['age'] ?></td>
                            <td><?= $row['phone'] ?></td>
                            <td><div class="wrapper">
                                    <button class="btn-start">Start</button>
                                    <!-- <button class="btn-stop">Stop</button> -->
                                    <button class="btn-reset">Reset</button>
                                    <p>
                                        <span class="mins">00</span>:<span class="seconds">00</span>:<span class="tens">00</span>
                                    </p> 
                                </div>
                            </td>
                        </tr>
                                    <?php
                                }
                            }
                        ?>
                       
                    </tbody>

                  </table>
            </div>
        </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
    <script src="https://code.jquery.com/jquery-3.6.1.min.js" integrity="sha256-o88AwQnZB+VDvE9tvIXrMQaPlFFSUTR+nldQm1LuPXQ=" crossorigin="anonymous"></script>
    <script src="script.js"></script>
</body>
</html>

Can anyone help me on this? TIA


Solution

  • The problem is that you are assigning your event listeners to a solitary set of buttons within the table by using querySelector rather than to them all. You could assign a listener to all buttons by iterating through the the collection returned by document.querySelectorAll() but an easier & cleaner approach would be to assign a delegated event listener to a common parent element that processes events registered upon it and delegates to whatever the intended target is to perform the task.

    You might wish to look at MDN's introduction to events and event delegation to get a fuller picture of what's involved as they will explain it better than I.

    The table seems like a good choice as the delegated target and, by adding a new className to each button ( such as timing-bttn as below ) you can identify which element has invoked the event by inspecting the event.target property.

    If you also modify the HTML that you generate within the PHP loop so that the div.wrapper element has a new dataset attribute, such as data-id, like this:

    <?php
        $sql = "SELECT * FROM patient_detailes";
        $query = mysqli_query($conn, $sql);
        if( $num = mysqli_num_rows($query) > 0 ){
            while($row = mysqli_fetch_array($query)){
    ?>
    <tr>
        <th scope="row"><?=$row['id']?></th>
        <td><?=$row['name']?></td>
        <td><?=$row['age']?></td>
        <td><?=$row['phone']?></td>
        <td>
            <div data-id='<?=$row['id']?>' class="wrapper"><!-- note the data-id attribute?¬ -->
                <button class="btn-start">Start</button>
                <button class="btn-reset">Reset</button>
                <p>
                    <span class="mins">00</span>:<span class="seconds">00</span>:<span class="tens">00</span>
                </p> 
            </div>
        </td>
    </tr>
    

    The benefit of this data-id attribute is that we can now store, within the global variable _timers, the reference to the setInterval call in relation to the data-id value ( assuming that each of these will be unique ) so the timers will be independently accessible.

    Then some potential rendered HTML might appear like depicted in the snippet:

    // utility / helper functions
    const d = document;
    const q = (e, n = d) => n.querySelector(e);
    const qa = (e, n = d) => n.querySelectorAll(e);
    //--------------------------------------
    const _TIME_INTERVAL = 10;
    // global variable to store references to each timer started
    // so that they can be controlled separately.
    const _timers = {};
    
    
    // set the span values to the original string values
    const initialise = (m, s, t) => {
      m.textContent = '00';
      s.textContent = '00';
      t.textContent = '00';
    };
    
    
    const startTimer = (m, s, t) => {
      // ensure display is set
      initialise(m, s, t);
      // find the numeric value from the spans
      let mins = Number(m.textContent);
      let secs = Number(s.textContent);
      let tens = Number(t.textContent);
    
      // return the timer that updates the individual spans
      return setInterval(() => {
        tens++;
    
        if (tens > 99) {
          secs++;
          tens = 0;
        }
        if (secs > 59) {
          mins++;
          secs = 0;
        }
    
        m.textContent = pad(mins);
        s.textContent = pad(secs);
        t.textContent = pad(tens);
      }, _TIME_INTERVAL);
    };
    
    // shorthand method to add zero to single digit
    const pad = function(i) {
      return (parseInt(i) < 10) ? '0' + parseInt(i) : parseInt(i);
    };
    
    
    
    
    
    
    
    
    
    
    
    /*
      Rather than assign the same event handler to each and every button
      assign a "Delegated Event Listener" to a common parent ( the Table )
      and respond to the click as appropriate to the logic within.
    */
    q('table.tablewatch').addEventListener('click', e => {
      if (e.target != e.currentTarget && e.target.classList.contains('timing-bttn')) {
        // find the SPAN parent node, the P
        let p = q('p', e.target.parentNode);
    
        // Find the dataset ID assigned (with PHP!) to the button parent element
        let id = e.target.parentNode.dataset.id;
    
        // find the SPAN elements within this wrapper
        let mins = q('.mins', p);
        let secs = q('.seconds', p);
        let tens = q('.tens', p);
    
        // either start or stop the timer
        switch (e.target.textContent) {
          case 'Start':
            _timers[id] = startTimer(mins, secs, tens);
            break;
    
          case 'Reset':
            initialise(mins, secs, tens);
            if (!isNaN(_timers[id])) clearInterval(_timers[id]);
            break;
        }
      }
    })
    table {
      border: 1px solid grey;
      width: 80%;
      font-family: monospace;
    }
    
    td,
    th {
      padding: 0.25rem;
      margin: 0.25rem;
      border: 1px dotted grey
    }
    
    th {
      background: darkgrey;
      color: white;
    }
    
    button {
      padding: 0.5rem;
      margin: auto 0.1rem;
      flex: 1;
    }
    
    tr td:nth-of-type(4) div.wrapper {
      display: flex
    }
    <table class="table table-bordered table-striped tablewatch">
      <thead>
        <tr>
          <th>Id</th>
          <th>Name</th>
          <th>Age</th>
          <th>Phone</th>
          <th>Waiting Time</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <th scope="row">23</th>
          <td>John Smith</td>
          <td>97</td>
          <td>0141 373 5780</td>
          <td>
            <div data-id=23 class="wrapper">
              <button class="timing-bttn btn-start">Start</button>
              <button class="timing-bttn btn-reset">Reset</button>
              <p>
                <span class="mins">00</span>:<span class="seconds">00</span>:<span class="tens">00</span>
              </p>
            </div>
          </td>
        </tr>
    
        <tr>
          <th scope="row">48</th>
          <td>Agnes Potter</td>
          <td>101</td>
          <td>0141 652 8544</td>
          <td>
            <div data-id=48 class="wrapper">
              <button class="timing-bttn btn-start">Start</button>
              <button class="timing-bttn btn-reset">Reset</button>
              <p>
                <span class="mins">00</span>:<span class="seconds">00</span>:<span class="tens">00</span>
              </p>
            </div>
          </td>
        </tr>
    
        <tr>
          <th scope="row">92</th>
          <td>Desdemona Nichols</td>
          <td>87</td>
          <td>0141 854 9685</td>
          <td>
            <div data-id=92 class="wrapper">
              <button class="timing-bttn btn-start">Start</button>
              <button class="timing-bttn btn-reset">Reset</button>
              <p>
                <span class="mins">00</span>:<span class="seconds">00</span>:<span class="tens">00</span>
              </p>
            </div>
          </td>
        </tr>
      </tbody>
    </table>


    To run all timers at page load

    // utility / helper functions
    const d = document;
    const q = (e, n = d) => n.querySelector(e);
    const qa = (e, n = d) => n.querySelectorAll(e);
    //--------------------------------------
    const _TIME_INTERVAL = 10;
    // global variable to store references to each timer started
    // so that they can be controlled separately.
    const _timers = {};
    
    
    
    
    
    const startTimer = (m, s, t) => {
      // ensure display is set
      initialise(m, s, t);
      
      // find the numeric value from the spans
      let mins = Number(m.textContent);
      let secs = Number(s.textContent);
      let tens = Number(t.textContent);
    
      // return the timer that updates the individual spans
      return setInterval(() => {
        tens++;
    
        if (tens > 99) {
          secs++;
          tens = 0;
        }
        if (secs > 59) {
          mins++;
          secs = 0;
        }
    
        m.textContent = pad(mins);
        s.textContent = pad(secs);
        t.textContent = pad(tens);
      }, _TIME_INTERVAL);
    };
    
    // shorthand method to add zero to single digit
    const pad = function(i) {
      return (parseInt(i) < 10) ? '0' + parseInt(i) : parseInt(i);
    };
    
    
    
    // set the span values to the original string values
    // modified to return current displayed values
    const initialise = (m, s, t) => {
      let value={m:m.textContent,s:s.textContent,t:t.textContent};
      m.textContent = '00';
      s.textContent = '00';
      t.textContent = '00';
      return value;
    };
    
    
    
    /* 
      Changes made below only other than the removal of the "start" buttons.
      Query the dom to find all div.wrapper elements so that we can then find the
      various SPAN elements within the P.
      The global `_timers` variable is once again keyed with the `dataset.id`
      and the return of `startTimer` function.
    */
    d.addEventListener('DOMContentLoaded',()=>{
      qa('table.tablewatch > tbody > tr > td > div.wrapper').forEach( div=>{
        let mins = q('p span.mins', div);
        let secs = q('p span.seconds', div);
        let tens = q('p span.tens', div);
        
        initialise( mins, secs, tens );
        _timers[ div.dataset.id ]=startTimer(mins, secs, tens);
      })
    });
    
    /*
      The stop buttons again use the `delegated listener` and work
      as previously. Added small piece of code that returns the current
      timer value when the button is clicked. This might be useful?!
    */
    q('table.tablewatch').addEventListener('click', e=>{
      if (e.target.classList.contains('timing-bttn') ) {
        let p = q('p', e.target.parentNode);
        let id = e.target.parentNode.dataset.id;
        let mins = q('.mins', p);
        let secs = q('.seconds', p);
        let tens = q('.tens', p);
        
        
        if ( !isNaN(_timers[id] ) ) {
          let current=initialise( mins, secs, tens );
          clearInterval( _timers[id] );
          
          console.info( 
            'Timer "%s" was at "%s" before being stopped at "%s"', 
            id,
            Object.values( current ).join(':'),
            new Date()
          );
        }
      }
    })
    table {
      border: 1px solid grey;
      width: 80%;
      font-family: monospace;
    }
    
    td,
    th {
      padding: 0.25rem;
      margin: 0.25rem;
      border: 1px dotted grey
    }
    
    th {
      background: darkgrey;
      color: white;
    }
    
    button {
      padding: 0.5rem;
      margin: auto 0.1rem;
      flex: 1;
    }
    
    tr td:nth-of-type(4) div.wrapper {
      display: flex
    }
    <table class="table table-bordered table-striped tablewatch">
      <thead>
        <tr>
          <th>Id</th>
          <th>Name</th>
          <th>Age</th>
          <th>Phone</th>
          <th>Waiting Time</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <th scope="row">23</th>
          <td>John Smith</td>
          <td>97</td>
          <td>0141 373 5780</td>
          <td>
            <div data-id=23 class="wrapper">
              <button class="timing-bttn btn-reset">Reset</button>
              <p>
                <span class="mins">00</span>:<span class="seconds">00</span>:<span class="tens">00</span>
              </p>
            </div>
          </td>
        </tr>
    
        <tr>
          <th scope="row">48</th>
          <td>Agnes Potter</td>
          <td>101</td>
          <td>0141 652 8544</td>
          <td>
            <div data-id=48 class="wrapper">
              <button class="timing-bttn btn-reset">Reset</button>
              <p>
                <span class="mins">00</span>:<span class="seconds">00</span>:<span class="tens">00</span>
              </p>
            </div>
          </td>
        </tr>
    
        <tr>
          <th scope="row">92</th>
          <td>Desdemona Nichols</td>
          <td>87</td>
          <td>0141 854 9685</td>
          <td>
            <div data-id=92 class="wrapper">
              <button class="timing-bttn btn-reset">Reset</button>
              <p>
                <span class="mins">00</span>:<span class="seconds">00</span>:<span class="tens">00</span>
              </p>
            </div>
          </td>
        </tr>
      </tbody>
    </table>