1

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 Answers 2

1

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/

Sign up to request clarification or add additional context in comments.

6 Comments

Thanks! That's excellent. I was so close... Just didn't get my head around using my object, instead of an anonymous one. Can I most the subscribe to the main view model though? Reason is, if the person enters '=B2'... I need to find the B2 cell, get the value, and use that value. Having the subscribe in the object it's self - I'm not sure I can access all the other cells.
Check the update. It's not exactly subscribed to the main view model, but the function is now within the main view model so you can access the other cells.
Thanks H77... I'm getting 'valueChanged' is not a function. Should it be declared as self.valueChanged instead?
The snippet above works ok if you run it. Maybe you've missed something? But yeah you can add it to self if you want. You'd have to change the caller too.
I must have,... Copied and pasted your lines, and all is perfect. Thanks for the help!
|
0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.