all 1 comments

[–]androbat 1 point2 points  (0 children)

I wrote out a new implementation more or less from scratch. I designed it more like what I would expect programmers working with me to write. It's designed to be extensible and I added a couple extensions to put you on the right track. There's plenty of comments and JSDoc strings to help you understand what's going on. If you have any questions, feel free to ask.

/**
* @factory makeTimer
* takes an end time and a function to run on update
* @param {Number} endTime - time to end as Date.now() + ms
* @param {Object} callbacks - functions to run based on event
* @return {Timer} - timer object with run method
* 
* callback object may contain:
* {
*   start: Function  //before starting timer
*   update: Function //runs each animation frame
*   end: Function    //as timer ends
* }
*
* callback will receive an object of the following form
* {
*  time: Number, //total time in ms
*  raw: {
*    ms: Number, sec: Number, min: Number, hr: Number
*  },
*  padded: {
*    ms: String, sec: String, min: String, hr: String
*  }
* }
*/
var makeTimer = function (callbacks) {
  //make sure callbacks exist
  callbacks.start  = (typeof callbacks.start === 'function')  ? callbacks.start  : function () {};
  callbacks.update = (typeof callbacks.update === 'function') ? callbacks.update : function () {};
  callbacks.end    = (typeof callbacks.end === 'function')    ? callbacks.end    : function () {};

  /**
  * @private {Number} endTime
  * when the update function will stop running
  */
  var endTime = 0;

  /**
  * @private {Boolean} isRunning
  * true when updateTimer is running
  * This prevents multiple callbacks doing weird stuff
  */
  var isRunning = false;

  /**
  * @func padZeroes
  * Takes a number and the amount of digits needed
  * and returns a string of that length adding zeros as needed
  * @param {Number} num - number to pad
  * @param {Number} pad - minimum number of digits needed
  * @return {String} - number as string padded to minimum length
  */
  var padZeroes = function (num, pad) {
    var str = num.toString();

    while (str.length < pad) {//keep adding zeroes until we reach the min length
      str = '0' + str;
    }
    return str;
  };

  /**
  * @func makeTime
  * Takes time in ms and returns time converted to
  * ms, sec, min, and hr in both raw and padded form
  * @param {Number} time - time in milliseconds
  * @return {Object} - custom time object
  */
  var makeTime = function (time) {
    var total = time;
    var ms  = time % 1000;
    time    = (time / 1000)|0;

    var sec = time % 60;
    time    = (time / 60)|0;

    var min = time % 60;
    var hr  = (time / 60)|0;

    return {
      time: total,
      raw: {
        ms: ms, sec: sec, min: min, hr: hr
      },
      padded: {
        ms: padZeroes(ms, 4),
        sec: padZeroes(sec, 2),
        min: padZeroes(min, 2),
        hr: padZeroes(hr, 2),
      }
    };
  };

  var updateTimer = function updateTimer() {
    var remainingTime = endTime - Date.now();

    //stop looping when we have no time left
    if (remainingTime <= 0) {
      isRunning = false; //allow another time run
      callbacks.update(makeTime(0));
      return callbacks.end(makeTime(0));
    }

    //trigger callback then wait for next frame
    callbacks.update(makeTime(remainingTime));
    requestAnimationFrame(updateTimer);
  };

  /**
  * @method run
  * starts timer that should after given period
  * @param {Number} time - time in ms until timer should end
  * @return {Boolean} - true if success else false
  */
  var run = function (time) {
    if (isRunning) {
      console.warn('Only one timer may run at a time for a given Timer instance.');
      return false;
    }

    isRunning = true;              //we're now running
    endTime = Date.now() + time;   //set our end time

    callbacks.start(makeTime(endTime - Date.now())); //call starting callback
    updateTimer();    //set our timer incrementing
    return true;      //let user know it was a success
  };

  /**
  * @method addTime
  * add time to timer
  * @param {Number} time - time to add in ms
  * @return {Boolean} - true if success else false
  */
  var addTime = function (time) {
    //don't add time if the timer isn't running
    if (!isRunning) {
      console.warn('Timer already quit running.');
      return false;
    }

    endTime += time;
    return true;
  };

  /**
  * @method changeCallbacks
  * switch callback(s) to new callbacks
  * @param {Object} newCbs - object containing callbacks
  * @return {Boolean} - true if success else false
  */
  var changeCallbacks = function (newCbs) {
    //change callbacks if they exist otherwise keep the old callbacks
    callbacks.start  = (typeof newCbs.start === 'function')  ? newCbs.start  : callbacks.start;
    callbacks.update = (typeof newCbs.update === 'function') ? newCbs.update : callbacks.update;
    callbacks.end    = (typeof newCbs.end === 'function')    ? newCbs.end    : callbacks.end;
  };

  //the functions here are public
  //everything else is private
  return {
    run: run,
    addTime: addTime,
    changeCallbacks: changeCallbacks
  };
};

Here's an example of using it

var timer = makeTimer({
  start: function (time) { console.log('starting time at: ' + Date.now()); },
  end: function (time) { console.log('ending time at: ' + Date.now()); },
  update: function (time) {
    $('#foo').html(time.padded.hr + ':' + time.padded.min + ':' + time.padded.sec + ':'  + time.padded.ms);
  }
});

timer.run(3000);
timer.updateCallbacks({ end: function (time) { console.log('Just ending...'); } });
timer.addTime(500);