views:

97

answers:

3

I'm having trouble understanding the scope of 'this' in this scenario. I'm able to call each one of these functions like: this.startTracking(); from within the time tracker switch object. However, when I try to execute the code: Drupal.timeTracker.prototype.stopTracking(); It loses all scope of variables and my GET request becomes undefined. How can I fire off stopTracking() onbeforeunload?

    Drupal.behaviors.dynamicTimeTracker = function (context) {
  $('form.node-time-tracker', context).each(function () {
    new Drupal.timeTracker(this);
  });
};

/**
 * A time tracker switch object
 */
Drupal.timeTracker = function (form) {
  var tracker = this;
  this.form = form;
  this.nid = $('#'+ form.id +' input[name="nid"]').attr('value');
  this.uid = $('#'+ form.id +' input[name="uid"]').attr('value');
  this.button = $('#'+ form.id +' input[type="submit"]');
  this.url = Drupal.settings.time_tracker.url + '/' + this.nid + '/' + this.uid;
  this.counter = $('#'+ form.id +' .counter');

  this.initialize(); // TODO: make sure this function is called regularly to make sure trackers are in synch
  this.startTracking();
  $(window).bind('beforeunload', function() {
    Drupal.timeTracker.prototype.stopTracking(); // need help here
  });
};

/**
 * Initialize the time tracker
 */
Drupal.timeTracker.prototype.initialize = function () {
  var tracker = this;

  $.ajax({
    type: "GET",
    url: tracker.url,
    dataType: 'json',
    success: function (status) {
      $(tracker.counter).countdown({compact:true, since:-status['time']}).countdown('resume');

      if (status['status'] == 'ongoing') {
        $(tracker.button).toggle(
          function() {
            tracker.stopTracking();
            return false;
          },
          function() {
            tracker.startTracking();
            return false;
          }
        );
        $(tracker.counter).countdown('resume');
        $(tracker.button).val(Drupal.t('Stop'));
        $(tracker.form).removeClass('node-time-tracker-start').addClass('node-time-tracker-stop');
      }
      else {
        $(tracker.button).toggle(
          function() {
            tracker.startTracking();
            return false;
          },
          function() {
            tracker.stopTracking();
            return false;
          }
        );
        $(tracker.counter).countdown('pause');
        $(tracker.button).val(Drupal.t('Start'));
        $(tracker.form).removeClass('node-time-tracker-stop').addClass('node-time-tracker-start');
      } 
    },
    error: function (xmlhttp) {
      alert(Drupal.ahahError(xmlhttp, tracker.startURL));
    }
  });
};

/**
 * Starts time tracking
 */
Drupal.timeTracker.prototype.startTracking = function () {
  var tracker = this;

  // Ajax GET request for starting time tracker
  $.ajax({
    type: "GET",
    url: tracker.url + '/start',
    dataType: 'json',
    success: function (status) {
      $(tracker.counter).countdown('change', {since: -status['time']}).countdown('resume');
      $(tracker.button).val(Drupal.t('Stop'));
      $(tracker.form).removeClass('node-time-tracker-start').addClass('node-time-tracker-stop');
    },
    error: function (xmlhttp) {
      alert(Drupal.ahahError(xmlhttp, tracker.startURL));
    }
  });
};

/**
 * Stops time tracking
 */
Drupal.timeTracker.prototype.stopTracking = function () {
  var tracker = this;

  // Ajax GET request for stopping time tracker
  $.ajax({
    type: "GET",
    url: tracker.url + '/stop',
    dataType: 'json',
    success: function (status) {
      $(tracker.counter).countdown('change', {since: -status['time']}).countdown('pause');
      $(tracker.button).val(Drupal.t('Start'));
      $(tracker.form).removeClass('node-time-tracker-stop').addClass('node-time-tracker-start');
    },
    error: function (xmlhttp) {
      alert(Drupal.ahahError(xmlhttp, tracker.startURL));
    }
  });
};
+1  A: 

I'll just take out a little snippet:

this.startTracking();
$(window).bind('beforeunload', function() {
  // this is defined by the above function definition
  tracker.stopTracking();
});

Your problem is, that when you create the bind function, this inside it, will refer to $(window), so you need to create a copy of this, to be able to reference it inside this new function.

googletorp
Thank you, that was exactly right... I appreciate your help!
A: 

googletorp's answer should work, but just a quick note:

I believe the issue you're having is because you're trying to call:

Drupal.timeTracker.prototype.stopTracking();

Instead, I think it should it be:

Drupal.timeTracker.stopTracking();

I don't believe you're supposed to call functions on the prototype, but rather on the object whose prototype you've modified.

netRealm
I did try this, and Firebug told me that my function was undefined
Interesting...I suppose I have more to learn about JS prototypes.
netRealm
@netRealm You don't inherit from the prototype until you instantiate the object (ie. you'd need to create an instance of `Drupal.timeTracker` to be able to use `stopTracking` without going through the prototype object)
indieinvader
A: 

When you call new Drupal.timeTracker(this), it creates a new object. That object inherits from Drupal.timeTracker.prototype. Inside the methods of that object, this is set to the object itself. Drupal.timeTracker.prototype is not the object, but merely a template from which new instances of the object may be formed; Like a cookie-cutter, it's not very good to eat. Most importantly, none of the inner state of your actual timer lives there.

When you call Drupal.timeTracker.prototype.stopTracking, it's being called on the template, not the real object. If you call tracker.stopTracking(), you'll be okay. You can't call this.stopTracking(), because when you want to call stopTracking, you're in the event handler for onbeforeunload, so your this will be the window object.

Sean McMillan