Search code examples
javascriptmvvmknockout.jsprototypecomputed-observable

viewModel computed functions on the prototype using knockout.js


I am using using the knockout.js library, which helps with data-binding. So I keep getting the error that my variables are not defined inside a computed function which is on we viewModel prototype. I know this is because the computed function is changing the context of "this" to the Window, but I can't seem to figure out how to get it to change back to the root(viewModel).The method I am referring to is "Messages" in the javascript. That being said, how do I change the context back to the viewModel?

Here is my code:

HTML

p#title.col-xs-12.bg-primary.text-center
  | Tic - Tac - Toe!
div.col-xs-3.bg-info
  div.bg-primary.controls
    span
      button.btn.btn-default(data-bind="click:StartMessage.bind($root)")
        | New Game
      p#message.lead(data-bind="text:Messages.bind($root)()")
table.bg-success(style="table-layout:fixed;")
  tr#row1
    td(data-bind="click:Messages.bind($root)")
    td &nbsp
    td &nbsp
  tr#row2
    td &nbsp 
    td &nbsp
    td &nbsp
  tr#row3
    td &nbsp 
    td &nbsp
    td &nbsp

JAVASCRIPT

var message = (function(){
  function Message(){
   this.main = ko.observable(true);
   this.welcome = "Welcome to Tic-Tac-Toe! This is a 2 player game. Click New Game to play!"
   this.turn = ", its your turn."
   this.win = ", you won!"
   this.draw = "It's a draw..."
  }
  return Message;
})()

var players = (function(){
  function Players(){
    this.player1 = ko.observable(true);
    this.player2 = ko.observable(false);
  }
  return Players;
})()

var aBox = (function(){
  function ABox(){
    this.symbol = ko.observable(" ")
  }

  return ABox;
})()

var viewModel = (function(){
  function ViewModel(){
    this.GameMessage = new message();
    this.thePlayers = new players();
    this.r1c1 = new aBox();
    this.r1c2 = new aBox();
    this.r1c3 = new aBox();
    this.r2c1 = new aBox();
    this.r2c2 = new aBox();
    this.r2c3 = new aBox();
    this.r3c1 = new aBox();
    this.r3c2 = new aBox();
    this.r3c3 = new aBox();

  }

/**************************************** 
 ************* Messages *****************
 ****************************************/ 

  ViewModel.prototype.StartMessage = function(){

     this.GameMessage.main(false)
  }

  ViewModel.prototype.Messages = ko.computed(function(){

    if(this.GameMessage.main()){
      return this.GameMessage.welcome;
    }
    else if(this.thePlayers.player1()){
      this.thePlayers.player1(false);
      this.thePlayers.player2(true);
      return "Player 1"+this.GameMessage.turn;

    }
    else if(this.thePlayers.player2())
      this.thePlayers.player1(true);
      this.thePlayers.player2(false);
      return "Player 2"+this.GameMessage.turn;
  },ViewModel)

  return ViewModel;
})()

ko.applyBindings(new viewModel())

I have experimented with changing the context to "viewModel" as shown,$root, and "this."

If you are wondering what the method is trying to accomplish, when the NEW MESSAGE button is clicked, it will trigger a message to be shown. Then if the <td> is clicked, it will display a different message in the place of the previous one.


Solution

  • ko.computed function include a second parameter that allows to bind the computed function this to whichever object you specify. (Behind the scenes it simply uses apply)

    So, when you're defining your computed in the prototype, you simply have to bind the computed to this, like so:

    ViewModel.prototype.Messages = ko.computed(function(){
      // your function code
      }, this);
    

    Remember that, as you're working with a prototype, this refers to the object instance you're interested in.