title: Ag-grid viewport: cannot read property 'bind' of undefined
body: <p>I'm replicating the ag-grid Viewport example putting it in an object representing the table instead of a anonymous function. I cannot understand why it gives me the error in the title when trying to connect to the mockserver, given that it is exactly the same code.</p>
  
  <p>Here it is the code that doesn't seem to work:</p>
  
  <pre><code>   function ViewportDatasource(mockServer) {
          this.mockServer = mockServer;
          this.connectionId = this.mockServer.connect(this.eventListener.bind(this));
      }
  
      ViewportDatasource.prototype.eventListener = function (event) {
          switch (event.eventType) {
              case 'rowCountChanged':
                  this.onRowCountChanged(event);
                  break;
              case 'rowData':
                  this.onRowData(event);
                  break;
              case 'dataUpdated':
                  this.onDataUpdated(event);
                  break;
          }
      };
  </code></pre>
  
  <p>here it is the whole object:</p>
  
  <pre><code>function WatcherTable($rootScope,$scope, $http) {
  
      this.FIELD_KEY_ID = "id";
      this.FIELD_KEY_ISSUER_SHORT_DESC= "isd";
      this.FIELD_KEY_ISSUER = "iss";
      this.FIELD_KEY_CUSIP = "cus";
      this.FIELD_KEY_ISIN = "isin";
      this.FIELD_KEY_BOARD_LABEL = "lbl";
      this.FIELD_KEY_IND_SECT_LABEL = "isl";
      this.FIELD_KEY_CURR="cur"
  
      this.FIELD_KEY_BEST_BID_SIZE = "bBidSz";
      this.FIELD_KEY_BEST_BID_PRICE = "bBidPrc";
      this.FIELD_KEY_BEST_ASK_PRICE = "bAskPrc";
      this.FIELD_KEY_BEST_ASK_SIZE = "bAskSz";
      this.FIELD_KEY_BEST_BID_SPREAD = "bBidSpd";
      this.FIELD_KEY_BEST_ASK_SPREAD = "bAskSpd";
  
      this.FIELD_KEY_ORDER_BID_SIZE = "oBidSz";
      this.FIELD_KEY_ORDER_BID_PRICE = "oBidPrc";
      this.FIELD_KEY_ORDER_ASK_PRICE = "oAskPrc";
      this.FIELD_KEY_ORDER_ASK_SIZE = "oAskSz";
      this.FIELD_KEY_ORDER_BID_SPREAD = "oBidSpd";
      this.FIELD_KEY_ORDER_ASK_SPREAD = "oAskSpd";
  
      this.headerMyOrder="ORDER";
      this.headerMyBidOrder="BID ORDER";
      this.headerMyAskOrder="ASK ORDER";
  
  
      this.cols = [{
          headerName : "Security Info",
          marryChildren : true,
          children : [{
              headerName : "Issuer Short Desc.",
              field : this.FIELD_KEY_ISSUER_SHORT_DESC,
  
              //width : 0,
              //hide : true
          },
          {
              headerName : "Industry Sector Label",
              field : this.FIELD_KEY_IND_SECT_LABEL,
              //width : 80,
              filter: 'set',
              filterParams: { values: ['Advertising', 'Aerospace/Defense', 'Agriculture', 'Airlines', 'Apparel', 'Auto Manufacturers', 'Auto Parts&amp;Equipment', 'Banks', 'Basic Materials', 'Beverages', 'Biotechnology', 'Building Materials', 'Chemicals', 'Coal', 'Commercial Services', 'Communications', 'Computers', 'Consumer, Cyclical', 'Consumer, Non-cyclical', 'Cosmetics/Personal Care', 'Distribution/Wholesale', 'Diversified', 'Electrical Compo&amp;Equip', 'Electronics', 'Energy', 'Energy-Alternate Sources', 'Engineering&amp;Construction', 'Entertainment', 'Machinery-Constr&amp;Mining', 'Household Products/WaresIndustrial', 'Insurance', 'Internet', 'Investment Companies', 'Iron/Steel', 'Kangaroos', 'Leisure Time', 'Lodging', 'Machinery-Constr&amp;Mining', 'Machinery-Diversified', 'Media', 'Metal Fabricate/Hardware', 'Mining', 'Miscellaneous Manufactur', 'Multi-National', 'Office Furnishings', 'Office/Business ', 'Oil&amp;Gas', 'Oil&amp;Gas Services', 'Packaging&amp;Containers', 'Pharmaceuticals', 'Pipelines', 'Real Estate', 'Regional(state/provnc)', 'REITS', 'Retail', 'Savings&amp;Loans'],
                              newRowsAction: 'keep'},
              },
          {
              headerName : "Board Label",
              field : this.FIELD_KEY_BOARD_LABEL,
              //width : 80,
              filter: 'text',
              filterParams: { apply: true }
  
          }, {
              headerName : "CUSIP",
              field : this.FIELD_KEY_CUSIP,
              //width : 150,
              //suppressFilter: true
          },
          {
              headerName : "ISIN",
              field : this.FIELD_KEY_ISIN,
              //width : 150,
              //suppressFilter: true
          },
          {
              headerName : "Currency",
              field : this.FIELD_KEY_CURR,
              //width : 150,
              //suppressFilter: true
          }
          ]},
          {
          headerName : "Best",
          marryChildren : true,
  
          children : [ {
              headerName : "Bid Size",
              pinned: 'right',
              field : this.FIELD_KEY_BEST_BID_SIZE,
              cellStyle: {'border-left': '1px solid #E82043', 'color':'#E82043', 'font-weight':'bold', 'text-align':'right'},
              cellRenderer: BidRenderer,
              filter:'number',
  
  
              //width : 125,
              //suppressFilter: true
          }, {
              headerName : "Bid Price",
              pinned: 'right',
              field : this.FIELD_KEY_BEST_BID_PRICE,
              cellStyle: {'border-right': '1px solid #E82043', 'color':'#E82043', 'font-weight':'bold', 'text-align':'right'},
              //cellRenderer: 'animateShowChange',
              cellFormatter: numberFormatter,
              cellRenderer: BidRenderer,
              width : 125,
              filter:'number',
  
  
              //suppressFilter: true
          },
          {
              headerName : "Ask Price",
              pinned: 'right',
              field : this.FIELD_KEY_BEST_ASK_PRICE,
              //cellRenderer: 'animateShowChange',
              cellFormatter: numberFormatter,
              cellRenderer: AskRenderer,
              cellStyle: {'color':'#19B092', 'font-weight':'bold', 'text-align':'right'},
              filter:'number',
              //width : 125,
              //suppressFilter: true
          },
          {
              headerName : "Ask Size",
              pinned: 'right',
              field : this.FIELD_KEY_BEST_ASK_SIZE,
              cellStyle: {'border-right': '1px solid #19B092', 'color':'#19B092', 'font-weight':'bold', 'text-align':'right'},
              cellRenderer: AskRenderer,
              filter:'number',
              //width : 125,
              //suppressFilter: true
          } ]
      } ];
  
  
      //definizione della tabella//// rowModelType: 'virtual', rowModelType: 'pagination',
  
  
  
      this.table = {
              showPrice: true,
              showSpread: true,
              orderSize: 0,
              orderFilter: '',
              enableServerSideFilter: true,
              enableServerSideSorting: true,
              sortingOrder: ['desc','asc',null],
              enableColResize : true,
              debug : true,
              rowSelection : 'multiple',
              rowDeselection : true,
              columnDefs : this.cols,
              rowModelType : 'viewport',
              headerHeight: 20,
              rowHeight: 20,
              viewportRowModelPageSize:30,
              viewportRowModelBufferSize: 15,         
              suppressColumnVirtualisation: true, 
              suppressMenuColumnPanel: true,
              onCellDoubleClicked: WatcherTable.prototype.onCellDoubleClicked.bind(this,$rootScope),          
              getContextMenuItems: WatcherTable.prototype.getContextMenuItems.bind(this,$rootScope),
              getMainMenuItems: WatcherTable.prototype.getMainMenuItems.bind(this, $rootScope), 
              getRowNodeId: function (data) {
                  // the code is unique, so perfect for the id
                  return data.isin;
              },
              onGridReady:setRowData($http)
      };
  
      function numberFormatter(params) {
          if (typeof params.value === 'number') {
              return params.value.toFixed(4);
          } else {
              return params.value;
          }
      }
  
  
  
      function BidRenderer () {}
  
      BidRenderer.prototype.init = function(params) {
          // create the cell
          this.eGui = document.createElement('div');
          this.eGui.innerHTML = '&lt;div&gt;&lt;/div&gt;';
  
          // set value into cell
          this.eValue = this.eGui.querySelectorAll('div')[0];
          this.eValue.innerHTML = params.valueFormatted ? params.valueFormatted : params.value;
  
      };
  
      // gets called once when grid ready to insert the element
      BidRenderer.prototype.getGui = function() {
          return this.eGui;
      };
  
      // gets called whenever the user gets the cell to refresh
      BidRenderer.prototype.refresh = function(params) {
          // set value into cell again
          this.eGui.innerHTML = '&lt;div class="bid"&gt;&lt;/div&gt;';
          this.eValue = this.eGui.querySelectorAll('div')[0];
          this.eValue.innerHTML = params.valueFormatted ? params.valueFormatted : params.value;
  
      };
  
      function AskRenderer () {}
  
      AskRenderer.prototype.init = function(params) {
          // create the cell
          this.eGui = document.createElement('div');
          this.eGui.innerHTML = '&lt;div&gt;&lt;/div&gt;';
  
          // set value into cell
          this.eValue = this.eGui.querySelectorAll('div')[0];
          this.eValue.innerHTML = params.valueFormatted ? params.valueFormatted : params.value;
  
      };
  
      // gets called once when grid ready to insert the element
      AskRenderer.prototype.getGui = function() {
          return this.eGui;
      };
  
      // gets called whenever the user gets the cell to refresh
      AskRenderer.prototype.refresh = function(params) {
          // set value into cell again
          this.eGui.innerHTML = '&lt;div class="ask"&gt;&lt;/div&gt;';
          this.eValue = this.eGui.querySelectorAll('div')[0];
          this.eValue.innerHTML = params.valueFormatted ? params.valueFormatted : params.value;
      };
  
      // client code (ie your code) will call this constructor, pass in whatever you need for the
      // viewport to do it's job
      function ViewportDatasource(mockServer) {
          this.mockServer = mockServer;
          this.connectionId = this.mockServer.connect(this.eventListener.bind(this));
      }
  
      // gets called by the grid, tells us what rows the grid is displaying, so time for
      // us to tell the server to give us the rows for that displayed range
      ViewportDatasource.prototype.setViewportRange = function (firstRow, lastRow) {
          this.mockServer.setViewportRange(this.connectionId, firstRow, lastRow);
      };
  
      // gets called by the grid, provides us with the callbacks we need
      ViewportDatasource.prototype.init = function (params) {
          this.params = params;
      };
  
      // gets called by grid, when grid is destroyed or this datasource is swapped out for another one
      ViewportDatasource.prototype.destroy = function () {
          this.mockServer.disconnect(this.connectionId);
      };
  
      // manages events back from the server
      ViewportDatasource.prototype.eventListener = function (event) {
          switch (event.eventType) {
              case 'rowCountChanged':
                  this.onRowCountChanged(event);
                  break;
              case 'rowData':
                  this.onRowData(event);
                  break;
              case 'dataUpdated':
                  this.onDataUpdated(event);
                  break;
          }
      };
  
      // process rowData event
      ViewportDatasource.prototype.onRowData = function (event) {
          var rowDataFromServer = event.rowDataMap;
          this.params.setRowData(rowDataFromServer);
      };
  
      // process dataUpdated event
      ViewportDatasource.prototype.onDataUpdated = function (event) {
          var that = this;
          event.changes.forEach(function (change) {
              var rowNode = that.params.getRow(change.rowIndex);
              // if the rowNode is missing, it means the grid is not displaying that row.
              // if the data is missing, it means the rowNode is there, but that data has not
              // loaded into it yet, so to early to set delta changes.
              if (!rowNode || !rowNode.data) {
                  return;
              }
              // rowNode.data[change.columnId] = change.newValue;
              // this is a trick, it gets the row to refresh
              rowNode.setDataValue(change.columnId, change.newValue);
          });
      };
  
      // process rowCount event
      ViewportDatasource.prototype.onRowCountChanged = function (event) {
          var rowCountFromServer = event.rowCount;
          // this will get the grid to make set the height of the row container, so we can scroll vertically properly
          this.params.setRowCount(rowCountFromServer);
      };
  
      function setRowData($http) {
          // set up a mock server - real code will not do this, it will contact your
          // real server to get what it needs
          var mockServer = new MockServer();
          $http.get('data.json').then(function(response){
              mockServer.init(response.data);
          });
  
          var viewportDatasource = new ViewportDatasource(mockServer);
          table.api.setViewportDatasource(viewportDatasource);
          // put the 'size cols to fit' into a timeout, so that the scroll is taken into consideration
          setTimeout(function () {
              table.api.sizeColumnsToFit();
          }, 100);
      }
  
      // setup the grid after the page has finished loading
     /* document.addEventListener('DOMContentLoaded', function () {
          var gridDiv = document.querySelector('#liveStreamExample');
          new agGrid.Grid(gridDiv, table);
  
          // do http request to get our sample data - not using any framework to keep the example self contained.
          // you will probably use a framework like JQuery, Angular or something else to do your HTTP calls.
          var httpRequest = new XMLHttpRequest();
          httpRequest.open('GET', 'data.json');
          httpRequest.send();
          httpRequest.onreadystatechange = function () {
              if (httpRequest.readyState == 4 &amp;&amp; httpRequest.status == 200) {
                  var httpResponse = JSON.parse(httpRequest.responseText);
                  setRowData(httpResponse);
              }
          };
      });*/
  
  };
  
  WatcherTable.prototype.getContextMenuItems = function ($rootScope,params){
      var result= [{name:"Show Book", action:WatcherTable.prototype.changeBookSubscription.bind(this,$rootScope,params.node.data)}];
  
      return result;
  }
  
  WatcherTable.prototype.onCellDoubleClicked = function ($rootScope,params){
      $rootScope.$broadcast("changeBookSubscription",{instrkey:params.data.cus+"."+params.data.isin,boardLabel:params.data.isd+" "+params.data.lbl});
      if(params.colDef.field.indexOf("bBid")&gt;-1){
          $rootScope.$broadcast("showHitDialog",params);
          log("Show hit dialog");
          console.log(params);
      }
      else if(params.colDef.field.indexOf("bAsk")&gt;-1){
          $rootScope.$broadcast("showLiftDialog",params);
          log("Show lift dialog");
          console.log(params);
      }
  }
  
  WatcherTable.prototype.changeBookSubscription = function ($rootScope,data) {
      $rootScope.$broadcast("changeBookSubscription",{instrkey:data.cus+"."+data.isin,boardLabel:data.isd+" "+data.lbl});
  }
  
  WatcherTable.prototype.getMainMenuItems = function($rootScope, params){
      var prcCols=["bBidPrc","bAskPrc","oBidPrc","oAskPrc"];
      var spdCols=["bBidSpd","bAskSpd","oBidSpd","oAskSpd"];
      var menuItems= [
          {name:'Show Price', 
              action:function(){
                          params.columnApi.setColumnsVisible(prcCols, !params.api.gridCore.gridOptions.showPrice);
                          params.api.gridCore.gridOptions.showPrice= !params.api.gridCore.gridOptions.showPrice;      
                      }, 
               checked: params.api.gridCore.gridOptions.showPrice
               },
          {name:'Show Spread', 
          action:function(){
                      params.columnApi.setColumnsVisible(spdCols, !params.api.gridCore.gridOptions.showSpread);
                      params.api.gridCore.gridOptions.showSpread= !params.api.gridCore.gridOptions.showSpread;        
                  }, 
           checked: params.api.gridCore.gridOptions.showSpread
           },
           {
               name:'Orders',
                  subMenu:[
                      {name:'Live Orders Only', 
                          action:function(){
                          if (params.api.gridCore.gridOptions.orderFilter==='live'){
                              params.api.gridCore.gridOptions.orderFilter='';
                              //TODO filter
                          }
                          else {params.api.gridCore.gridOptions.orderFilter='live';
                              //TODO filter
                          }
  
                      }, checked:(params.api.gridCore.gridOptions.orderFilter==='live')},
                      {name:'My Orders Only',     action:function(){
                          if (params.api.gridCore.gridOptions.orderFilter==='mine'){
                              params.api.gridCore.gridOptions.orderFilter='';
                              //TODO filter
                          }
                          else {params.api.gridCore.gridOptions.orderFilter='mine';
                              //TODO filter
                          }
  
                      }, checked:(params.api.gridCore.gridOptions.orderFilter==='mine')},
                      {name:'My Firm\'s Orders Only', action:function(){
                          if (params.api.gridCore.gridOptions.orderFilter==='firm'){
                              params.api.gridCore.gridOptions.orderFilter='';
                              //TODO filter
                          }
                          else {params.api.gridCore.gridOptions.orderFilter='firm';
                              //TODO filter
                          }
  
                      }, checked:(params.api.gridCore.gridOptions.orderFilter==='firm')},
                  ]
           },
           {name: 'Size ...', action: function() {
  
               console.log(params.api.gridCore.gridOptions.orderSize);
               $rootScope.$broadcast("orderSizeDialog",{size:parseInt(params.api.gridCore.gridOptions.orderSize)});
              },
              }
      ];
      return menuItems;
  }
  </code></pre>
  
  <p>Can you help me?</p>
  
  <p><strong>EDIT</strong></p>
  
  <p>I noticed that eventListener function isn't being called when creating ViewportDatasource object. How can I force it?</p>
  
  <p><strong>EDIT 2</strong></p>
  
  <p>I took ViewportDatasource declaration out of WatcherTable scope. Now I got over the starting issue, but I cannot load data into the grid.</p>
  
  <p>I created a plunker: <a href="https://plnkr.co/edit/EEEJULRE72nbPF6G0PCK" rel="nofollow noreferrer">https://plnkr.co/edit/EEEJULRE72nbPF6G0PCK</a></p>