Search code examples
javascripthtmlknockout.jsknockout-mvc

Knockout JS : click-to-edit in table


first of all Im using Knockout js. So I have a table that I can add and remove rows from it dynamically, my problem is that I want to add a click-to-edit in the table for each row but it doesn't work. once I add a second row Im not enable to edit. Here is my code, you can just copy and past it JSFiddle and it will explain further what Im saying.

Here is my code:

(function () {
var ViewModel = function () {
    var self = this;

    //Empty Row
    self.items = ko.observableArray([]);

		self.editing = ko.observable(true);
    
    self.edit = function() { this.editing(true) }
    
    self.addRow = function () {
        self.items.push(new Item());            
    };

    self.removeRow = function (data) {
        self.items.remove(data);
    };        
}

var Item = function (fname, lname, address) {
    var self = this;
    self.firstName = ko.observable(fname);
    self.lastName = ko.observable(lname);
    self.address = ko.observable(address);
};

vm = new ViewModel()
ko.applyBindings(vm);

})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" />

<table class="table table-bordered">
  <thead class="mbhead">
    <tr class="mbrow">
      <th>Input</th>
      <th>First Name</th>
      <th>Last Name</th>
      <th>Address</th>
      <th>Actions</th>
    </tr>
  </thead>
  <tbody data-bind="foreach: items">
    <tr>
      <td>
        <select class="form-control common-input-text" data-bind="event: { change: $root.addNewItem }">
          <option value="">One</option>
          <option value="">Two</option>
          <option value="">Three</option>
        </select>
      </td>
      <td>
        <b data-bind="uniqueName: true,visible: !($parent.editing()), text: firstName, click: function() { $parent.editing(true) }"></b>
        <input data-bind="uniqueName: true, visible: $parent.editing, value: firstName, hasFocus: $parent.editing" />
      </td>
      <td><span class="input-small" data-bind="value: lastName" /></td>
      <td><span class="input-small" data-bind="value: address" /></td>
      <td>
        <input type="button" value="Remove Row" data-bind="click: $parent.removeRow" class="btn btn-danger" />
      </td>
    </tr>
  </tbody>
</table>
<input type="button" value="Add Row" class="btn btn-primary" data-bind="click: addRow" />

thank you for your help


Solution

  • The problem lies in creating a new row that bounds an observable to hasFocus:

    <input data-bind="uniqueName: true, 
                      visible: $parent.editing, 
                      value: firstName, 
                      hasFocus: $parent.editing" /> <-- the problem cause
    

    On row creation, the previously-focused row loses focus, which causes editing to be set to false.

    So the solution would be to just use the observable value (instead of bounding the observable itself):

    <input data-bind="uniqueName: true, 
                      visible: $parent.editing, 
                      value: firstName, 
                      hasFocus: $parent.editing()" /> // <-- call the observable
    

    But better yet is to add an observable into Item view model, called isFocused, and use it instead:

    var Item = function (fname, lname, address) {
        var self = this;
        self.isFocused = ko.observable(true);
        // ... other observables ...
    };
    
    <input data-bind="uniqueName: true, 
                      visible: isFocused(), 
                      value: firstName, 
                      hasFocus: isFocused" />