0

I'm trying to call an object method from another method within the same constructor, and it doesn't seem to be working - I just get the error TypeError: undefined is not a function (evaluating 'this.uiDisplayOptions()').

I'm instantiating the object using using the new keyword var ui = new _ui().

Anyone know why it's not working? I've seen examples of this kind of setup being suggested.

Here's the code:

function _ui() {
this.uiDisplayOptions = function() {
    var len =  channels[currentChannel].stepsChannel;
    $("select#length option")
        .prop('selected', false)
        .filter('[value="' + len + '"]')
        .prop('selected', true);
    var rand = channels[currentChannel].randomAmtChannel;
    $("select#randomness option")
        .prop('selected', false)
        .filter('[value="' + rand + '"]')
        .prop('selected', true);
    var dir = channels[currentChannel].readDirection;
    $("select#readdirection option")
        .prop('selected', false)
        .filter('[value="' + dir + '"]')
        .prop('selected', true);
}

this.uiSetListeners = function() {
    // Select Channel
    $("#selectChannel0").addClass("green");
    $(".channelselect").click(function() {
        $(".channelselect").removeClass("green");
        $(this).addClass("green");
        currentChannel = $(this).data("channel");
        displayUpdateChannel();
        this.uiDisplayOptions();
    });

    // Select Row
    $("#selectRow0").addClass("red");
    $("#selectRow0").click(function() {
        currentRow = 0;
        $("#selectRow1").removeClass("red");
        $(this).addClass("red");
    });
    $("#selectRow1").click(function() {
        currentRow = 1;
        $("#selectRow0").removeClass("red");
        $(this).addClass("red");
    });

    // Increment/Decrement Selected Row Pattern
    $("#patternInc").click(function() {
        selectPatternRow(1);
        displayPattern();
    });
    $("#patternDec").click(function() {
        selectPatternRow(-1);
        displayPattern();
    });

    // Shift Left/Right Selected Row Pattern
    $("#shiftLeft").click(function() {
        selectShiftRow(-1);
        displayPattern();
    });
    $("#shiftRight").click(function() {
        selectShiftRow(1);
        displayPattern();
    });

    // Handle Row 'Pattern Locks'
    $(".lock").click(function() {
        var step = $(this).data("lockstep");
        switch(toggleLockBit(step)) {
            case 0:
                $(this).removeClass("red green");
                break;
            case 1:
                $(this).addClass("red");
                break;
            case 2:
                $(this).removeClass("red").addClass("green");
                break;
        }
        displayPattern();
    });

    // Handle Channel Length change
    $("#length").change(function() {
        selectCurrentChannelLength($(this).val());
        displayChannelLength();
    });

    // Handle Channel Randomness change
    $("#randomness").change(function() {
        selectCurrentChannelRandomAmt($(this).val());
        displayRandomAmt();
    });
}

}

Dan
  • 10,282
  • 2
  • 37
  • 64

3 Answers3

1
  this.uiSetListeners = function() {
    // Select Channel
    $("#selectChannel0").addClass("green");
    $(".channelselect").click(function() {
      $(".channelselect").removeClass("green");
      // this here does not refer to the this of the object being created.
      // it refers to the anonymous function being created in the click call.
      // jQuery is probably invoking this and binding this to undefined,
      // but even if it wasn't then this code would behave incorrectly.

      $(this).addClass("green");
      currentChannel = $(this).data("channel");
      displayUpdateChannel();
      this.uiDisplayOptions();
    });
  });

When inside a function the this's value may change. It has it's own binding called a ThisContext and cannot be guaranteed to be pointing at the object you are calling this from within (especially with the introduction of bind, apply and call). Inside uiSetListeners, this is generally bound to the function (which in turn is bound to the object, assuming you are invoking the constructor correctly, and not using any bind magic).

However inside your click, handler, you are delegating the function to jQuery. jQuery doesn't know about your object so it doesn't bind this (or binds it to undefined), and it isn't associated with an object by default (as the function is being declared anonymously and not bound to an object). In other words, your click handler is pointing to a different this than your this.uiSetListeners statement is.

The way to fix this is by using a var that = this; kind of mechanism. If you take this approach, you should probably define var that = this at the top of your constructor function (so others can see what's going on) and replace any incidence of this inside of the constructor function with that.

This ensures that should another user call your constructor with call, bind et al, the object will be bound correctly to the supplied this.

    var that = this;
    that.uiSetListeners = function() {
      // Select Channel
      $("#selectChannel0").addClass("green");
      $(".channelselect").click(function() {
        $(".channelselect").removeClass("green");
        $(this).addClass("green");
        currentChannel = $(that).data("channel");
        displayUpdateChannel();
        that.uiDisplayOptions();
    });
  });

Note that ES6 fixes this with the fat arrow notation.

this.uiSetListeners = function() {
  // Select Channel
  $("#selectChannel0").addClass("green");
  $(".channelselect").click(() => {
    $(".channelselect").removeClass("green");
    $(this).addClass("green");
    currentChannel = $(this).data("channel");
    displayUpdateChannel();
    this.uiDisplayOptions();
  });
});

You should be perfectly able to invoke other methods from within the constructor as long as you remember to take care with your this.

It is generally preferable to use YourConstructor.prototype.methodName instead, as this will first of all reduce nesting but also uses the prototype chain. Assigning functions to this in the constructor does not assign them to the prototype chain, which also means they will be recreated each time a new object is created. You only really need to assign functions to this inside of a Constructor if their implementation is dependent on the values passed into the constructor and it is not appropriate to capture those values in the constructor as state on the created object.

Dan
  • 10,282
  • 2
  • 37
  • 64
  • I'll try the prototype method too. I'll only be creating one instance of my object in this case, so that should work fine, and be more efficient, as you suggest. – Alex Drinkwater May 03 '15 at 10:59
  • Another benefit is inheritance, you cannot extend your class as is unless you use the constructor because the methods you assign in the constructor are not on the prototype chain. – Dan May 03 '15 at 12:26
  • I can't seem to get the method above to work with the prototype declaration method. I'm happy for the moment to declare the object methods in the constructor, since it works. To put things in context, this is part of a JS simulation of a system that will eventually end up as the Arduino-based firmware for a piece of music hardware, so as long as this part of the system works, I'll leave it as-is. – Alex Drinkwater May 03 '15 at 20:16
  • Incidentally, that's why I'm referencing lots of global functions and variables. It's common to work this way in the Arduino world, though I'm aware it's frowned upon in Javascript circles. – Alex Drinkwater May 03 '15 at 20:18
  • I have no knowledge of Arduino systems other than the use of them in my previous employment position. If it works for you, it works. It's just a guideline. No point assigning to the prototype if you don't need the inheritance (but just be wary of the performance benefits) – Dan May 03 '15 at 22:34
  • Indeed. Thanks very much for all your help, Dan. – Alex Drinkwater May 04 '15 at 07:45
0

you can't call a function like this inside a constructor, this will refer to global object window until you call your constructor function using new keyword.

var ui = new  _ui();
meOn
  • 194
  • 1
  • 12
0

refer the current object context on the top of you constructor function.

function _ui() {
  var _that = this;
}

and refer all current constructor function using _that reference.

I hope it will solve your problem.

meOn
  • 194
  • 1
  • 12
  • You shouldn't need to create multiple answers to the same question - just modify your original answer. @AlexDrinkwater it can be done, and it is a common idiom. See my answer. You need to place `var _that = this;` at the top of your constructor function, *before any usage of `_that`*. – Dan May 03 '15 at 10:00
  • Hi Dan, thanks for the detailed answer. I will try your suggestion as soon as I get a chance (away from the computer at the moment). – Alex Drinkwater May 03 '15 at 10:53
  • Apologies also if my posting etiquette isn't great. I Haven't used this forum much, so I'm not quite sure yet how things work. – Alex Drinkwater May 03 '15 at 10:55