views:

77

answers:

2

In JavaScript, what is the proper way to reference an object's method within the object itself?

I'm messing around with Safari Extensions, and I'm trying to make an extension that changes the background color of a div based on the extension's settings. I'm using the following code:

function SwapColors() {
  this.bgColorMe = safari.extension.settings.bgColorMe;
  this.styleSheet = safari.extension.addContentStyleSheet(this.getStyleSheet());

  this.getStyleSheet = function() {
    return 'div.me {background-color: ' + this.bgColorMe + ' !important}';
  }

  this.rebuildStyleSheet = function() {
    safari.extension.removeContentStyleSheets();
    this.styleSheet = safari.extension.addContentStyleSheet(this.getStyleSheet());
  }
}

var colors = new SwapColors;

safari.extension.settings.addEventListener("change", colors.rebuildStyleSheet,false);

However, Safari is complaining on line 3 (the this.styleSheet declaration):

TypeError: Result of expression 'this.getStyleSheet' [undefined] is not a function.

I'm not really proficient in JavaScript, but it seems like this should work: this.getStyleSheet() is defined later in the object, so it shouldn't matter, right?

Figuring that might not be true, I tried moving the this.styleSheet declaration after getStyleSheet() and it works: when the object is created, styleSheet gets the correct value.

But now Safari complains about the same thing on line 11 within rebuildStyleSheet() when I reset styleSheet, even though getStyleSheet() is before rebuildStyleSheet().

So obviously I'm missing something. How can I call getStyleSheet() in the places it needs to be called within the object?


Edit

Based on cHao's answer, I was able to get functioning code:

function SwapColors() {
  this.getStyleSheet = function() {
    return 'div.me { background-color: ' + this.bgColorMe + ' !important}';
  }

  this.rebuildStyleSheet = function(event) {
    safari.extension.removeContentStyleSheets();

    if (event.key == "bgColorMe") {
      this.bgColorMe = event.newValue;
    }

    this.styleSheet = safari.extension.addContentStyleSheet(this.getStyleSheet());
  }

  this.bgColorMe = safari.extension.settings.bgColorMe;
  this.styleSheet = safari.extension.addContentStyleSheet(this.getStyleSheet());
}

var colors = new SwapColors;

safari.extension.settings.addEventListener("change", function(event) { 
  colors.rebuildStyleSheet(event); 
}, false);

But this only works as long as I declare this.styleSheet after this.getStyleSheet(), which is contrary to how every other object-oriented language I know handles similar objects. Is this just a quirk of JavaScript, or is there something else I'm missing?


Edit 2

meder's solution turned out to be the one I was looking for. My final code:

function SwapColors() {
  var that = this;

  this.bgColorMe = safari.extension.settings.bgColorMe;
  this.styleSheetURL = safari.extension.addContentStyleSheet(getStyleSheet());
  this.styleSheet = getStyleSheet();

  this.rebuildStyleSheet = function(event) {
    safari.extension.removeContentStyleSheet(that.styleSheetURL);

    if (event.key == "bgColorMe") {
      that.bgColorMe = event.newValue;
    }

    styleSheetURL = safari.extension.addContentStyleSheet(getStyleSheet());
  }

  function getStyleSheet() {
    return 'div.me { background-color: ' + that.bgColorMe + ' !important}';
  }
}

var colors = new SwapColors;

safari.extension.settings.addEventListener("change", colors.rebuildStyleSheet, false); 
+1  A: 

I think you should store a reference to the execution context. Change your this to the name of the variable within your constructor which stores a reference to the execution context.

function SwapColors() {
  var that = this;
  this.bgColorMe = safari.extension.settings.bgColorMe;
  this.styleSheet = safari.extension.addContentStyleSheet(this.getStyleSheet());

  this.getStyleSheet = function() {
    return 'div.me {background-color: ' + that.bgColorMe + ' !important}';
  }

  this.rebuildStyleSheet = function() {
    safari.extension.removeContentStyleSheets();
    that.styleSheet = safari.extension.addContentStyleSheet(that.getStyleSheet());
  }
}

Otherwise if you don't do this, this refers to safari.extension.settings which is the current execution context. There is no getStyleSheet method for that object.

meder
With some fudging, this worked and does exactly what I expect the object to do. Perfect, thanks! I've edited my question with the final solution.
Mark Trapp
+2  A: 

You'll need to do something like

safari.extension.settings.addEventListener("change", function() { colors.rebuildStyleSheet(); }, false);

in order for the methods to know what "this" means. Otherwise, it's just called as a normal function, and "this" won't be colors, but some other object (probably safari.extension.settings).

cHao
This solved the second problem: thank you! Why is it I can't call this.getStyleSheet() if this.styleSheet is declared before it, though?
Mark Trapp