Search code examples
javascriptmeteormeteor-blazehandsontable

Loading data correctly on handsontable with Meteor and blaze


I'm using handsontable on Meteor 1.4.1 through the plugin awsp:handsontable@0.16.1

The problem I have is that the matrix gets re-rendered every time I change a value, which creates two issues. The first is that the focus of the edited cell gets lost and the scroll goes back to the top. The second is that sometimes the values are not saved because the data of the matrix get reloaded with each change.

The way I'm subscribing to the data and rendering the table is as follows:

Template.connectivityMatrix.onCreated(function () {
  this.activeScenario = () => Session.get('active_scenario');

  this.autorun(() => {
    this.subscribe('connectivityMatrixUser', this.activeScenario());
  });
});

Template.connectivityMatrix.onRendered(function () {
  this.autorun(() => {
    if (this.subscriptionsReady()) {
      const activeScenario = Session.get('active_scenario');
      const currentScenario = Scenarios.findOne({_id: activeScenario});
      const currentTurn = currentScenario.turn;
      const numObj = ConnectivityMatrix.find({scenario_id: activeScenario, user_id: Meteor.userId(), turn: currentTurn}).count();

      var myData = [];  // Need this to create instance
      var container = document.getElementById('connectivity-matrix');

      var hot = new Handsontable(container, { // Create Handsontable instance
        data: myData,
        startRows: numObj,
        startCols: numObj,
        afterChange: function (change, source) {  // 'change' is an array of arrays.
          if (source !== 'loadData') {  // Don't need to run this when data is loaded
            for (i = 0; i < change.length; i++) {   // For each change, get the change info and update the record
              var rowNum = change[i][0]; // Which row it appears on Handsontable
              var row = myData[rowNum];  // Now we have the whole row of data, including _id
              var key = change[i][1];  // Handsontable docs calls this 'prop'
              var oldVal = change[i][2];
              var newVal = change[i][3];
              var setModifier = {$set: {}};   // Need to build $set object
              setModifier.$set[key] = newVal; // So that we can assign 'key' dynamically using bracket notation of JavaScript object
              ConnectivityMatrix.update(row._id, setModifier);
            }
          }
        }
      });

      myData = ConnectivityMatrix.find({scenario_id: activeScenario, turn: currentTurn, user_id: Meteor.userId()}, {sort: {created_at: 1}}).fetch();  // Tie in our data
      hot.loadData(myData);
    }
  });
});

What I want to achieve is to create the matrix only once instead of recreate it with each data change so the focus stays and the data gets always saved. So I've tried leaving only the last two lines inside the block of this.autorun() as suggested in this question

Template.connectivityMatrix.onRendered(function () {
  const activeScenario = Session.get('active_scenario');
  const currentScenario = Scenarios.findOne({_id: activeScenario});
  const currentTurn = currentScenario.turn;
  const numObj = ConnectivityMatrix.find({scenario_id: activeScenario, user_id: Meteor.userId(), turn: currentTurn}).count();

  var hot = new Handsontable(container, { // Create Handsontable instance
    ...
  });

  this.autorun(() => {
    if (this.subscriptionsReady()) {
      myData = ConnectivityMatrix.find({scenario_id: activeScenario, turn: currentTurn, user_id: Meteor.userId()}, {sort: {created_at: 1}}).fetch();  // Tie in our data
      hot.loadData(myData);
    }
  });
});

but then the first time I load the page, the data is not available so I get the error

Cannot read property 'turn' of undefined

Therefore, how can I properly get all the data needed to create the table without re-rendering it when a cell value changes?

Thanks in advance for any help.


Solution

  • The way I managed to do what I needed is by stopping the computation after the matrix gets rendered. The code is as follows:

    Template.connectivityMatrix.onRendered(function () {    
      this.autorun((computation) => {
        if (this.subscriptionsReady()) {    
          const currentScenario = Scenarios.findOne({_id: activeScenario});
          const currentTurn = currentScenario.turn;
          const numObj = ConnectivityMatrix.find({scenario_id: activeScenario, user_id: Meteor.userId(), turn: currentTurn}).count();
    
          var myData = [];  // Need this to create instance
          var container = document.getElementById('connectivity-matrix');
    
          var hot = new Handsontable(container, { // Create Handsontable instance
            data: myData,
            colHeaders: arrayRowsCols,
            rowHeaders: arrayRowsCols,
            height: '450',
            maxRows: numObj,
            maxCols: numObj,
            columns: columns,
            afterChange: function (change, source) {  // 'change' is an array of arrays.
              if (source !== 'loadData') {  // Don't need to run this when data is loaded
                for (i = 0; i < change.length; i++) {   // For each change, get the change info and update the record
                  var rowNum = change[i][0]; // Which row it appears on Handsontable
                  var row = myData[rowNum];  // Now we have the whole row of data, including _id
                  var key = change[i][1];  // Handsontable docs calls this 'prop'
                  var oldVal = change[i][2];
                  var newVal = change[i][3];
                  var setModifier = {$set: {}};   // Need to build $set object
                  setModifier.$set[key] = newVal; // So that we can assign 'key' dynamically using bracket notation of JavaScript object
                  ConnectivityMatrix.update(row._id, setModifier);
                }
              }
            }
          });
    
          myData = ConnectivityMatrix.find({scenario_id: activeScenario, turn: currentTurn, user_id: Meteor.userId()}, {sort: {created_at: 1}}).fetch();  // Tie in our data
          hot.loadData(myData);
          computation.stop();
        }
      });
    });