1
votes

I'm trying to create a simple spreadsheet using Knockout. I'm trying to make each cell observable, so that on changes, I can evaluate the value and calculate accordingly. So if they person enters 6+7 in a cell, I can evaluate and change the value to the total.

However, I can't get each cell to be observable. Maybe I am going about it wrong.

I have tried to create a fiddle, but am now battling to get jquery loaded. So although I can run it within Visual Studio locally, the fiddle is complaining about $. (Any help fixing that would be great).

http://jsfiddle.net/tr9asadp/1/

I generate my observable array like this: self.RowCount = ko.observable(0); self.ColumnCount = ko.observable(0);

self.Columns = ko.observableArray([]);
self.Rows = ko.observableArray([]);

self.Refresh = function () {

    for (i = 0; i < self.RowCount(); i++) {
        var obj = {
            data: i + 1,
            calculated: i,
            rowNum: i,
            colNum: 0,
            columns: ko.observableArray([])
        };

        for (j = 0; j < self.ColumnCount(); j++) {
            obj.columns.push(ko.observable({
                label: self.Letters[j],
                value: j + 1,
                colIndex: j, 
                rowIndex: i
            }));
        }
        self.Rows.push(obj);

    }
    self.ShowSheet(self.RowCount() > 0 && self.ColumnCount() > 0);

I render a table based on the column and rows entered by the user (For now, limited to 5 by 5, as I using an array to convert 1,2,3 (columns) to A,B,C. But that's temporary and will be fixed.

How can I get each cell to be observable so that I can subscribe and fire an event on change?

2

2 Answers

1
votes

You don't seem to have made use of cellObject (from your fiddle). If you add objects of type cellObject to the rows and have an observable in there for value you can subscribe to changes on that.

Fixed code:

var cellObject = function() {
  var self = this;
  self.data = ko.observable();
  self.calculated = ko.observable();
  self.rowNum = ko.observable(0);
  self.colNum = ko.observable(0);
  self.rows = ko.observableArray([]);
  self.value = ko.observable();
}

function SpreadsheetViewModel() {
  var self = this;
  self.ShowSheet = ko.observable(false);
  self.ShowSheet(false);

  self.Letters = ['A', 'B', 'C', 'D', 'E']


  self.RowCount = ko.observable(0);
  self.ColumnCount = ko.observable(0);

  self.Columns = ko.observableArray([]);
  self.Rows = ko.observableArray([]);

  function valueChanged(newValue) {
    console.log("Value changed to " + newValue);
  }

  self.Refresh = function() {

    for (i = 0; i < self.RowCount(); i++) {
      var row = {
        cells: ko.observableArray([])
      };

      for (j = 0; j < self.ColumnCount(); j++) {
        var cell = new cellObject();
        cell.label = self.Letters[j];
        cell.data(i + 1);
        cell.calculated(i);
        cell.rowNum(i);
        cell.colNum(j);
        cell.value(j + 1);

        cell.value.subscribe(valueChanged);
        row.cells.push(cell);
      }
      self.Rows.push(row);

    }
    self.ShowSheet(self.RowCount() > 0 && self.ColumnCount() > 0);
  }

  self.Refresh();

}

var vm = new SpreadsheetViewModel();
ko.applyBindings(vm);
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>

<div id="spreadsheetSection">

  <div class="row">
    <div class="col-xs-3 text-right">No. of Columns</div>
    <div class="col-xs-2">
      <input type="text" class="form-control" placeholder="Columns" data-bind="value: ColumnCount">
    </div>
    <div class="col-xs-3 text-right">No. of Rows</div>
    <div class="col-xs-2">
      <input type="text" class="form-control" placeholder="Rows" data-bind="value: RowCount">
    </div>
    <div class="col-xs-2">
      <button class="btn btn-default" data-bind="click: Refresh">Refresh</button>
    </div>
  </div>
  <div class="row">
    <!-- ko if: ShowSheet -->
    <table class="table table-bordered table-hover table-striped">
      <tbody>
        <tr data-bind="foreach: Rows()[0].cells">
          <td>
            <span data-bind="text: label"></span>
          </td>

        </tr>
      </tbody>
      <tbody data-bind="foreach: Rows">
        <tr data-bind="foreach: cells">
          <td>
            <input type="text" class="form-control" data-bind="value: value">
          </td>
        </tr>
      </tbody>
    </table>
    <!-- /ko -->
  </div>
</div>

Fixed fiddle: https://jsfiddle.net/tr9asadp/3/

0
votes

I used a writableComputable http://knockoutjs.com/documentation/computed-writable.html so that if you type 1 + 1 in one of the cells and tab out, it will change to 2. here is the updated fiddle. http://jsfiddle.net/tr9asadp/5/

function column(label, value, colIndex, rowIndex ){
var self = this;
this.label = ko.observable(label);
this.value = ko.observable(value);
this.colIndex = ko.observable(colIndex);
this.rowIndex = ko.observable(rowIndex);
this.writableValue = ko.pureComputed({
        read: function () {
            return self.value();
        },
        write: function (v) {
           self.value(eval(v))
        },
        owner: this
    });
}