0

I'm building a form where one of the fields gets the value of a stopwatch countdown. When stopped, the time elapsed should be submitted as part of the data. The stopwatch JS function works perfectly if rendered as an H1, but doesn't work when I try to render the same countdown as a form input value and I have no idea why. Any help would be greatly appreciated. Specifically, it's the input value (incrementing digits) that does not render at all..it remains blank. A test function will work to alert "hello" as the onclick, but the desired function does not, though the same thing works outside the form. Here's my code:

// Define vars to hold time vakues
let seconds = 0;
let minutes = 0;
let hours = 0;

// Define var to hold setinterval function
let interval = null;

// Define var to hold stopwatch status
let status = "stopped";

// Stopwatch logic to determine when to increment next value, etc.
function stopwatch() {
  seconds++;

  // logic to determine when to increment next value
  if (seconds / 60 === 1) {
    seconds = 0;
    minutes++;

    if (minutes / 60 === 1) {
      minutes = 0;
      hours++;
    }
  }

  // If seconds/minutes/hours is only one digit, add a leading 0 to the values
  if (seconds < 10) {
    displaySeconds = "0" + seconds.toString();
  } else {
    displaySeconds = seconds.toString();
  }

  if (minutes < 10) {
    displayMinutes = "0" + minutes.toString();
  } else {
    displayMinutes = minutes.toString();
  }

  if (hours < 10) {
    displayHours = "0" + hours.toString();
  } else {
    displayHours = hours.toString();
  }

  // Display updated time values (increments in real time) to user
  document.getElementById("display").value = displayHours + ":" + displayMinutes + ":" + displaySeconds;
}

// Function to start / stop the watch
function startStop() {
  if (status === "stopped") {
    // Start the Stopwatch  (by calling the setInterval function)
    interval = window.setInterval(stopwatch, 1000);
    // Changes "start" button to "stop" button
    document.getElementById("startStop").innerHTML = "Stop";
    document.getElementById("startStop").style = "background: red";
    status = "started";

  } else {
    window.clearInterval(interval);
    document.getElementById("startStop").innerHTML = "Start";
    status = "stopped";
  }
}


// Function to reset the Stopwatch
function reset() {
  window.clearInterval(interval);
  seconds = 0;
  minutes = 0;
  hours = 0;

  document.getElementById("duration").value = "00:00:00";
  // Changes "stop" button to "start" button
  document.getElementById("startStop").innerHTML = "Start";
}
<form action="success.php" method="post">
  <label for="session-duration" class="form-label">duration: </label>
  <input type="text" name="session-duration" id="duration">
  <button type="button" name="button" id="startStop" onclick="startStop()">Start</button>
  <button type="button" name="button" id="reset" onclick="reset()">Reset</button>
  <input type="submit" name="session-submit" value="SUBMIT">
</form>
cloned
  • 6,346
  • 4
  • 26
  • 38
Argut
  • 1
  • 5
  • Is this the complete code? You don't seem to update the "duration" element value at any point other than to reset it to 00:00:00. – Geat Mar 11 '21 at 00:40
  • Yes it is, the sequence starts by calling the stopwatch function within the startStop function: if(status === "stopped"){ // Start the Stopwatch (by calling the setInterval function) interval = window.setInterval(stopwatch, 1000); – Argut Mar 11 '21 at 01:15
  • Actually I just figured out that accessing html element inside the form tags causes it to fail. Not sure why, but when I remove the form tags it works, so I will try to just keep the desired input as an H1 or something outside the form, access the value with JS and submit the value as a hidden input. – Argut Mar 11 '21 at 01:18
  • do you get error `Uncaught TypeError: startStop is not a function` ? – Rio A.P Mar 11 '21 at 03:21
  • @Rap Sherlock, yes I do actually, which makes no sense, as the function is defined below. I have a paired down version of this that works, but without the styling, etc..but I can't figure out what is going wrong here. Here is a Pen with the simple version which minus some styling and elements is the same as what's not working - https://codepen.io/aguaclara/pen/Jjbwbro – Argut Mar 11 '21 at 03:54
  • Here's a pen with the failing version: https://codepen.io/aguaclara/pen/wvoRojm – Argut Mar 11 '21 at 04:04
  • i'm in mobile cant help you much but `Uncaught TypeError: startStop is not a function` is from `id="startStop" onclick="startStop()"` inside form id and function with same name will be causing conflict [here the detail](https://stackoverflow.com/a/9160009/7058111) – Rio A.P Mar 11 '21 at 09:45

2 Answers2

0

The current issue is that the onclick attribute, like other event attributes does pass the element on which the event occurred as the context, if you will, it's like calling the event handler with with( target ) {, so if you try to access a variable that is defined as a property of that element, it will get this instead of getting the one of the global scope (window).

<button type="button" onclick="console.log(type)">log "button"</button>

Except that when in a <form> that form element gets used as the context. And since your element has its id set to "stopWatch", the stopWatch property of that <form> element points to this <button>, and so does the bare stopWatch variable in the event handler.

function foo() {
  console.log("I'm not gonna be logged");
}
console.log( "from form", document.querySelector( "form" ).foo );
<form>
  <button type="button" id="foo" onclick="console.log('from button\'s click', foo)">click me</button>
</form>

So when you click that button or the id="reset" one, it will try to call the element as a function, which fails.

The quick-fix is to change the ids of your elements so they don't override the function's name, and since HTMLFormElement.reset() is a thing, you'd have to change that function's name too:

// define vars to hold time vakues
let seconds = 0;
let minutes = 0;
let hours = 0;

// Define var to hold setinterval function
let interval = null;

// Define var to hold stopwatch status
let status = "stopped";

//stopwatch logic to determine when to increment next value, etc.
function stopwatch() {
  seconds++;

  // logic to determine when to increment next value
  if (seconds / 60 === 1) {
    seconds = 0;
    minutes++;

    if (minutes / 60 === 1) {
      minutes = 0;
      hours++;
    }
  }

  // If seconds/minutes/hours is only one digit, add a leading 0 to the values
  if (seconds < 10) {
    displaySeconds = "0" + seconds.toString();
  } else {
    displaySeconds = seconds.toString();
  }

  if (minutes < 10) {
    displayMinutes = "0" + minutes.toString();
  } else {
    displayMinutes = minutes.toString();
  }

  if (hours < 10) {
    displayHours = "0" + hours.toString();
  } else {
    displayHours = hours.toString();
  }

  // Display updated time values to user
  document.getElementById("display").innerHTML = displayHours + ":" + displayMinutes + ":" + displaySeconds;
}

// Function to start / stop the watch
function startStop() {
  if (status === "stopped") {
    // Start the Stopwatch  (by calling the setInterval function)
    interval = window.setInterval(stopwatch, 1000);
    document.getElementById("startStop_").innerHTML = "Stop";
    document.getElementById("startStop_").style = "background:red";
    status = "started";

  } else {

    window.clearInterval(interval);
    document.getElementById("startStop_").innerHTML = "Start";
    document.getElementById("startStop_").style = "background:green";
    let time = document.getElementById("display").innerHTML;
//    document.getElementById("time-hidden").value = time;
    status = "stopped";
  }
}


// Function to reset the Stopwatch
function resetCounter() {
  window.clearInterval(interval);
  seconds = 0;
  minutes = 0;
  hours = 0;

  document.getElementById("display").innerHTML = "00:00:00";
  document.getElementById("startStop_").innerHTML = "Start";
}
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">


<div class="container" style="width: 60%;">
  <div class="jumbotron text-left" style="padding: 10px 0px 20px; height: 400px;">
    <h4 style="margin-left: 20px; color: blue;">
      <?php echo $row['clinic_name']. " / " . $row['hours'] . "  hrs "; ?>
    </h4>
    <form class="" action="tr_create_session_success.php" method="post">
      <label for="session-date" class="form-label" style="display: inline; color: blue; margin-left: 20px; font-weight: lighter; ">date: </label>
      <input type="date" name="session-date" class="form-control" style="width: 23%; display: inline; margin-bottom: 10px;">
      <label for="session-duration" class="form-label" style="display: inline; color: blue; margin-left: 20px; font-weight: lighter; ">duration: </label>

      <span id="display" style="font-weight: bold; font-size: 1.2em;">00:00:00</span>
      <button type="button" name="button" id="startStop_" class="btn btn-success btn-sm" onclick="startStop()" style="margin: 0 0 5px 3px">Start</button>
      <button type="button" name="button" id="reset_" class="btn btn-light btn-sm" onclick="resetCounter()" style="margin: 0 0 5px 3px">Reset</button>

      <textarea name="session-notes" rows="8" cols="130" class="new-status" placeholder="notes..."></textarea>
      <input type="submit" name="tr-session-submit" class="btn btn-primary btn-lg" value="SUBMIT" style="margin: 0px 2% 0px 5px">
    </form>

  </div>
</div>

But a better fix would be to not use onevent attributes, ever.

Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • Thank you!! I changed the element id to something else and it worked! Would have never thought this was creating a conflict since in another context/version the same naming was working. Thanks again. – Argut Mar 11 '21 at 13:00
  • @Argut https://stackoverflow.com/help/someone-answers – Kaiido Mar 11 '21 at 13:45
-1

Here is a better StopWatch:

//<![CDATA[
/* js/etxternal.js */
let doc, htm, bod, nav, M, I, mobile, S, Q, hC, aC, rC, tC, hmsd, StopWatch; // for reuse on other loads
addEventListener('load', ()=>{
doc = document; htm = doc.documentElement; bod = doc.body; nav = navigator; M = tag=>doc.createElement(tag); I = id=>doc.getElementById(id);
mobile = nav.userAgent.match(/Mobi/i) ? true : false;
S = (selector, within)=>{
  let w = within || doc;
  return w.querySelector(selector);
}
Q = (selector, within)=>{
  let w = within || doc;
  return w.querySelectorAll(selector);
}
hC = (node, className)=>{
  return node.classList.contains(className);
}
aC = function(node, ...classNames){
  node.classList.add(...classNames);
  return aC;
}
rC = function(node, ...classNames){
  node.classList.remove(...classNames);
  return rC;
}
tC = function(node, className){
  node.classList.toggle(className);
  return tC;
}
hmsd = (milliseconds, returnString = null)=>{
  let t = milliseconds, hf = 3600000, mf = 60000, sf = 1000, h = Math.floor(t/hf);
  let hm = h*hf, m = Math.floor((t-hm)/mf), mm = m*mf+hm, s = Math.floor((t-mm)/sf);
  let d = t-(s*sf+mm);
  if(h < 10)h = '0'+h;
  if(m < 10)m = '0'+m;
  if(s < 10)s = '0'+s;
  if(d < 10){
    d = '00'+d;
  }
  else if(d < 100){
    d = '0'+d;
  }
  if(returnString === true){
    h = ''+h; m = ''+m; s = ''+s; d = ''+d;
  }
  else if(returnString === false){
    h = +h; m = +m; s = +s; d = +d;
  }
  return {h:h, m:m, s:s, d:d}
}
StopWatch = function(watchNode, milliseconds = true, interval = 0){
  this.watchNode = watchNode; this.milliseconds = milliseconds; 
  this.interval = interval;
  let m = 0, now = 0, it;
  const fun = ()=>{
    const o = hmsd((now ? Date.now()-now : 0));
    this.current = watchNode.textContent = o.h+':'+o.m+':'+o.s+(this.milliseconds ? '.'+o.d : '');
  }
  this.start = ()=>{
    if(!it){
      now = now+Date.now()-m; it = setInterval(fun, this.interval);
    }
    return this;
  }
  this.stop = ()=>{
    if(it){
      clearInterval(it); it = undefined; m = Date.now();
    }
    return this;
  }
  this.reset = ()=>{
    this.stop(); now = m = 0;
    this.current = watchNode.textContent = this.milliseconds ? '00:00:00.000' : '00:00:00';
    return this;
  }
  this.reset();
}
// tiny Library above - magic below can be put on another page using a load event *(except line with // end load)*
const stopWatch = new StopWatch(I('watch')), start_stop = I('start_stop');
const reset = I('reset');
let go = false;
start_stop.onclick = function(){
  if(go){
    stopWatch.stop(); rC(this, 'stop'); this.textContent = 'START';
  }
  else{
    stopWatch.start(); aC(this, 'stop'); this.textContent = 'STOP';
  }
  go = !go;
}
reset.onclick = ()=>{
  go = false; stopWatch.reset(); rC(start_stop, 'stop'); start_stop.textContent = 'START';
}
}); // end load
//]]>
/* css/external.css */
*{ /* set font deeper or white space may occur */
  box-sizing:border-box; font:0; padding:0; margin:0;
}
html,body,.full{
  width:100%; height:100%;
}
body{
  background:#ccc;
}
#wrapper,#stop_watch,#stop_watch>*{
  display:flex; justify-content:center; align-items:center; flex-wrap:wrap;
}
#stop_watch{
  width:334px; background:#333; padding:10px; border:4px double #fff; border-radius:5px;
}
#watch{
  width:314px; background:#000; color:white; font:bold 40px monospace; margin-bottom:10px;
}
#stop_watch>button{
  font:bold 22px Tahoma, Geneva, sans-serif;
}
button{
  cursor:pointer; background:linear-gradient(#1b7bbb,#147); color:#fff; border:1px solid #007; 
  font:bold 28px Tahoma, Geneva, sans-serif; padding:5px 15px; border:1px solid #007;
  border-radius:10px;
}
#start_stop{
  background:linear-gradient(#679467,#235023); border:1px solid #070; margin-right:7px;
}
#start_stop.stop{
  background:linear-gradient(#ad2323,#721818); border:1px solid #700; margin-right:8px;
}
#start_stop,#reset{
  width:calc(50% - 4px);
}
<!DOCTYPE html>
<html lang='en'>
  <head>
    <meta charset='UTF-8' /><meta name='viewport' content='width=device-width, height=device-height, initial-scale:1, user-scalable=no' />
    <title>Title Here</title>
    <link type='text/css' rel='stylesheet' href='css/external.css' />
    <script src='js/external.js'></script>
  </head>
<body>
  <div class='full' id='wrapper'>
    <div id='stop_watch'>
     <div id='watch'></div>
     <button id='start_stop'>START</button><button id='reset'>RESET</button>
    </div>
  </div>
</body>
<html>
</html>

Too busy to explain, but though I'd throw it in, in case it helps. Hope I got the hours right. Didn't let it run that far, but should work.

StackSlave
  • 10,613
  • 2
  • 18
  • 35
  • 1
    Thank you for this stopwatch. I ended up using it as I keep getting errors with the other code. This works perfectly, thanks again. – Argut Mar 11 '21 at 18:30