0

I making a simple game that uses a two minute JavaScript timer. I can get the javascript timer to work without using backbone. The code is at the bottom for the working timer, and here's a fiddle of it http://jsfiddle.net/mjmitche/hDRjR/19/

However, once I try to the timer code into a few different methods inside a Backbone view, I'm getting an error depending on how a key method, displayTime, code is defined

Steps:

1) I create a new clockView and save it to a variable clock_view

var clock_view    = new ClockView({ model: game}); 

Inside the initializer of clockview, I set up these variables that are used by the timer code

var totalWait = 120;
var secondsRemaining = totalWait;
var hasFocus = true;
var hasJustFailed = false;

The startClock method gets triggered from elsewhere

 this.model.bind("gameStartedEvent", this.startClock, this);

startClock uses setInterval to call displayTime method every second. Depending on how displayTime is coded [a) displayTime(), b) this.displayTime(), c) clock_view.displayTime() ], displayTime triggers a different error.

startClock: function(){
      console.log("start clock");

      setInterval(function(){
      this.secondsRemaining -= 1;
      console.log("working");
      displayTime();  //uncaught reference error: displayTime is not defined
      this.displayTime(); //Uncaught TypeError: Object [object Window] has no method 'displayTime' 
      clock_view.displayTime();// `display time gets called but triggers NAN`
      if(secondsRemaining == 0) $('#timer').fadeOut(1000);
      }, 1000);


    },

If displayTime is called from setInterval as displayTime() it says it's not defined. If I do this.displayTime(), I get a object window has no method. If I call it clock_view.displayTime(), it triggers a NAN error, which I think may be caused because the way the variables are defined in the initializer displayTime is defined directly below startClock like this

displayTime:  function () {
        var minutes = Math.floor(secondsRemaining / 60);
        var seconds = secondsRemaining - (minutes * 60);
        if (seconds < 10) seconds = "0" + seconds;
        var time = minutes + ":" + seconds;
        $('#timer').html(time);
    },

Update

This is a fiddle of the whole ClockView in a Backbone format, although it doesn't work because it's missing other parts of the program (such as the model that triggers the event). I'm including it only to make the question more readable

http://jsfiddle.net/mjmitche/RRXnK/85/

Original working clock code http://jsfiddle.net/mjmitche/hDRjR/19/

var displayTime = function () { var minutes = Math.floor(secondsRemaining / 60); var seconds = secondsRemaining - (minutes * 60); if (seconds < 10) seconds = "0" + seconds; var time = minutes + ":" + seconds; $('#timer').html(time); }; $('#timer').css('marginTop', 0);

setInterval(function(){
    secondsRemaining -= 1;
    displayTime();
    if(secondsRemaining == 0) $('#timer').fadeOut(1000);
}, 1000);
BrainLikeADullPencil
  • 11,313
  • 24
  • 78
  • 134

1 Answers1

2

This should work. The main points are, how variables are accessed inside the View.

HTML:

<div id="view">
    <div id="timer"> 
        <span class="time">2:00</span>
    </div>
    <div id="options">
        <input type="button" class="action_button" value="New Game" id="new_game">
    </div>
</div>

View:

var ClockView = Backbone.View.extend({
el: '#view',
initialize: function () {
    /** Define your variables in this View */
    this.totalWait = 120;
    this.secondsRemaining = this.totalWait;
    this.hasFocus = true;
    this.hasJustFailed = false;
},
events: {
    'click #new_game' : 'startClock' /** The button starts the clock */
},
startClock: function () {
    console.log("start clock");
    var self = this; /** Save 'this' to a local variable */
    setInterval(function () {
        self.secondsRemaining -= 1;

        self.displayTime();
        if (self.secondsRemaining == 0) self.$('#timer').fadeOut(1000);
    }, 1000);
},
displayTime: function () { 
        var minutes = Math.floor(this.secondsRemaining / 60);
        var seconds = this.secondsRemaining - (minutes * 60);
        if (seconds < 10) seconds = "0" + seconds;
        var time = minutes + ":" + seconds;
        this.$('#timer').html(time);
    },
});

var clock_view = new ClockView();
Dennis Rongo
  • 4,611
  • 1
  • 25
  • 25
  • Should be `self.$('#timer').fadeOut(1000);` :) – Paul Hoenecke Feb 02 '13 at 01:31
  • lol, you're right. I didn't wait until the 2:00 to expire to discover the bug. ;) – Dennis Rongo Feb 02 '13 at 01:32
  • Oddly enough, `this.$('#timer')` is actually fine. Calling on `this.displayTime();` doesn't work and has to use self. – Dennis Rongo Feb 02 '13 at 01:37
  • Ya because `this` is the window in that callback, but `self.$('#timer')` only looks [within the view](http://backbonejs.org/#View-dollar). But either way! – Paul Hoenecke Feb 02 '13 at 01:44
  • thanks very much. If you could explain why I need to use this for the definition of variables instead of var, it would help a lot. I have read about 'this' before, but it's still sinking in, so a quick explanation in this context would help me get it more. Thanks if you can help. – BrainLikeADullPencil Feb 02 '13 at 01:47
  • Within the view, `this` is usually the view, since you call a function via `clock-view.someFunction()`. But the `window` calls the callback you pass to `setInterval` so in that callback, `this` is the window. It can be confusing but check out [this](http://stackoverflow.com/questions/3127429/javascript-this-keyword). Also do some googling about scope in javascript to understand more about `var`. – Paul Hoenecke Feb 02 '13 at 02:00
  • 1
    When you declared `var totalWait = 120` inside `initialize` function, the variable is only accessible within that context of function. Making it `this.totalWait` made it accessible throughout the `View` since you're attaching it the `View` itself which is the `this`. If you want to avoid having to use `self` within the `setInteral()`, you can also use ` setInterval($.proxy(function () { this.secondsRemaining -= 1; this.displayTime(); if (this.secondsRemaining == 0) this.$('#timer').fadeOut(1000); },this), 1000);` to preserve `this`. – Dennis Rongo Feb 02 '13 at 02:47