0

I am trying to make a counter with JS. So far I've managed to pull that off, however I now encounter a problem. While using AJAX to retreive the time to count down I can't make it work. It's weird because it works on my original file but not with a php file called by AJAX.

This works fine : https://jsfiddle.net/6kvp25vv/

I have no idea what the problem is. This is the HTML page :

<button onclick="upgrade('meat_max')" id="up_meat_max">+</button>

When I click on the button, it runs the function inside this js file which creates a GET request on upgrade.php :

function upgrade(building) {
  var file = 'upgrade.php?building=' + building;
  ajax(file, function(response) {
    document.getElementById('construction').innerHTML += response;
  })
}

function ajax(file, fn) {
  var xmlhttp = new XMLHttpRequest();
  xmlhttp.onreadystatechange = function() {
    if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
      fn(xmlhttp.responseText);
    }
  };
  xmlhttp.open('GET', file, true);
  xmlhttp.send();
}

And this is the code from upgrade.php (variables sent to this file with AJAX are not used for the purpose of testing the code) :

<div class="time">Time: <span id="timer">?</span></div>
  var hour = 2;
  var minute = 46;
  var second = 45;

  // function to make a counter
  function clockIt() {
    function clockO(digit) {
      if(digit<10) {
        return '0';
      } else {
        return '';
      }
    }

    document.getElementById('timer').textContent = hour + ':' + clockO(minute) + minute + ':' + clockO(second) + second;
    if(second>0) {
      second -= 1;
    } else if(minute>0) {
      minute -= 1;
      second += 59;
    } else if(hour>0) {
      hour -= 1;
      minute += 59;
    }
  }

  // runs the function every seconds
  clockIt();
  setInterval(function (){clockIt()}, 1000);
Ivan
  • 34,531
  • 8
  • 55
  • 100

3 Answers3

3

innerHTML does not execute ajax loaded scripts, what I would do in your case is to return a JSON encoded string with the variables that you need and have a function on your main script (the one thats already loaded) with this provided script, that way you already have the function ready and only pass parameters with the ajax response.

You can decode a json string with:

obj = JSON.parse(jsonString);

For example:

Ajax JSON response string:

{"time": {"hour":2, "minute":46, "second": 45}, "html": "<div class=\"time\">Time: <span id=\"timer\"></span></div>"}

Modified upgrade function:

function upgrade(building) {
  var file = 'upgrade.php?building=' + building;
  ajax(file, function(response) {
    obj = JSON.parse(response);
    time = obj.time;
    document.getElementById('construction').innerHTML += obj.html;
    startCountdown(time.hour, time.minute, time.second);
  })
}

New function

function startCountdown(hour, minute, second) {

    // function to make a counter
    function clockIt() {
      function clockO(digit) {
        if(digit<10) {
          return '0';
        } else {
          return '';
        }
     }

    document.getElementById('timer').textContent = hour + ':' +     clockO(minute) + minute + ':' + clockO(second) + second;
     if(second>0) {
        second -= 1;
     } else if(minute>0) {
       minute -= 1;
       second += 59;
     } else if(hour>0) {
       hour -= 1;
       minute += 59;
     }
 }

 // runs the function every seconds
 clockIt();
 setInterval(function (){clockIt()}, 1000);
}
Miguel Suarez
  • 192
  • 4
  • 11
  • 1
    Thank you @Miguel, I didn't realise the AJAX loaded script wasn't running at all... I get the idea now, but how do I send the HTML part from the php file in JSON ? I am talking about that bit :
    Time: ?
    – Ivan Jul 04 '16 at 21:53
  • 1
    Ok, so I need to make sure to escape all special characters in the HTML code inside JSON. Thanks again @Miguel – Ivan Jul 04 '16 at 22:15
  • Yes if you are using PHP you could just use json_encode(): http://php.net/manual/en/function.json-encode.php – Miguel Suarez Jul 04 '16 at 22:18
  • Great, I'll try it out – Ivan Jul 04 '16 at 22:34
0

My problem was with the method for the countdown timer. When you specify an interval of 1000 milliseconds, you can't count on it being 1000 milliseconds. It's actually as soon as the timer loop gets to it after 1000 milliseconds have passed. Over a period of time, there are going to be some delays. What you want to do, if you want accurate timing, is to store the initial settings and then measure the time from when the countdown was started to the current time. See the code below which bases the timer on the internal clock rather than the interval counter. Once you have the number of seconds, you can easily it convert it to hours, minutes, and seconds by dividing by 3600 for the hours and using division and modulo arithmetic for the minute and second.

See https://www.sitepoint.com/creating-accurate-timers-in-javascript/

How to create an accurate timer in javascript?

<!DOCTYPE html />
<html>

<head>
  <meta encoding="UTF-8" />
  <title>Testing XMLHttpRequest</title>
  <script>
    var request;
    var button1;
    var display1;
    var display2;
    var display3;
    var start;
    var counter;

    function second() {
      display2.value = display2.value + "\r\nreadyState=" + request.readyState + " status=" + request.status + "\r\n";;
      if (request.readyState == 4 && request.status == 200) {
        display1.value = display1.value + request.responseText + "\r\n";
      }
    }

    function first() {
      display2.value = display2.value + "\r\n" +
        "Starting page     \r\n";
      request = new XMLHttpRequest();
      request.onreadystatechange = second;
      var file = "http://localhost:80/";
      request.open('GET', file, true);
      request.send();
      setInterval(timed, 1000);
    }

    function starter() {
      display1 = document.getElementById("display1");
      display2 = document.getElementById("display2");
      display3 = document.getElementById("display3");
      button1 = document.getElementById("button1");
      button1.onclick = first;
      start = new Date();
      counter = 60;
    }

    function timed() {
      var duration = (start.getTime() - new Date().getTime()) / 1000.0;
      display3.value = (duration + counter).toFixed(0);
    }

    window.onload = starter;
  </script>

</head>

<body>
  <form>
    <p>
      <input type="button" id="button1" value="Start" />Timer:
      <input type="text" readonly="readonly" id="display3" />
    </p>
    <p>Status:
      <textarea rows="5" cols="30" id="display2"></textarea>
    </p>
    <p>Response:
      <textarea rows="60" cols="80" id="display1"></textarea>
    </p>
  </form>
</body>

</html>
Community
  • 1
  • 1
Bradley Ross
  • 445
  • 2
  • 8
  • I understand there's a delay at each loop, isn't easier to use my version and check and correct the timer if it's too far off the accurate timer (calculate with the starting time and finishing time) ? – Ivan Jul 04 '16 at 23:29
0

Found a way to compensate the delays, using the original version :

function upgrade(building) {
  var file = 'upgrade.php?building=' + building;
  ajax(file, function(response) {
    var obj = JSON.parse(response);
    var time = obj.time;
    document.getElementById('construction').innerHTML += obj.html;
    run_clockIt(time.hour, time.minute, time.second);
  })
}

// general AJAX launcher
function ajax(file, fn) {
  var xmlhttp = new XMLHttpRequest();
  xmlhttp.onreadystatechange = function() {
    if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
      fn(xmlhttp.responseText);
    }
  };
  xmlhttp.open('GET', file, true);
  xmlhttp.send();
}


// count down timer with adjustments to compensate delay
function clockIt(hour, minute, second, finished, nbOfLoop) {

  // every 5 seconds, run a delay correction
  if(nbOfLoop%5 == 0) {
    var actualTimeLeft = adjustClock(finished);
    minute = actualTimeLeft[0];
    second = actualTimeLeft[1];
  }
  nbOfLoop += 1;

  // add a string "0" if necessary
  function clockO(digit) {
    if(digit<10) {
      return '0';
    } else {
      return '';
    }
  }

  document.getElementById('timer').textContent = hour + ':' + clockO(minute) + minute + ':' + clockO(second) + second;

  // update displayed timer
  if(second>0) {
    second -= 1;
  } else if(minute>0) {
    minute -= 1;
    second += 59;
  } else if(hour>0) {
    hour -= 1;
    minute += 59;
  }

  // waits 1 sec before launching the next one
  setTimeout(function() {
    clockIt(hour, minute, second, finished, nbOfLoop);
  }, 1000);
}

// runs the function for the first time
function run_clockIt(hour, minute, second) {
  var finished = new Date();
  finished.setUTCHours(finished.getUTCHours() + hour);
  finished.setUTCMinutes(finished.getUTCMinutes() + minute);
  finished.setUTCSeconds(finished.getUTCSeconds() + second);

  clockIt(hour, minute, second, finished, 1);
}

function adjustClock(finished) {
  var now = new Date();
  var diff = new Date(Math.abs(now - finished));

  return [diff.getUTCMinutes(), diff.getUTCSeconds()];
}

This way, the count down timer is smooth, no lagging and most of all you can define the interval on which the adjust function will run and correct the timer.

This is the php file, preparing the JSON object (thanks @Miguel) :

<?php
header('Content-Type: application/json');

// retreive variables from AJAX call
$building = $_REQUEST['building'];

// processing variable with database...

// prepare JSON object
$jsonData = '
{
  "time":{"hour":2, "minute":46, "second": 45},
  "html": "<div class=\"time\">Time: <span id=\"timer\"></span></div>"
}
';

echo $jsonData;
Ivan
  • 34,531
  • 8
  • 55
  • 100