Lobosan Lobosan - 3 months ago 23
Javascript Question

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.

Answer

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();
    }
  });
});