JSFiddle - React, Tailwind, and code Playground

by rniemeyer

HTML

<div data-bind="with: product">
  <button data-bind="click: beginEdit">Edit</button>
  <button data-bind="click: endEdit">Cancel</button>
  <input type="checkbox" data-bind="checked: amIEditing" disabled
  />
  <input type="text" data-bind="value: name" />
  <hr />
  <div data-bind="foreach: variants">
    <div>
      <button data-bind="click: beginEdit">Edit</button>
      <button data-bind="click: endEdit">Cancel</button>
      <input type="checkbox" data-bind="checked: amIEditing" disabled
      />
      <input type="text" data-bind="value: name" />
    </div>
  </div>
</div>
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>

JavaScript

var Entity = function () {
  this.name = ko.observable("default");
  this.meta = {};
  this.meta.original = ko.observable();
  this.meta.isEditing = ko.observable(false);
};

ko.utils.extend(Entity.prototype, {
  beginEdit: function () {
    this.meta.isEditing(true);
  },
  endEdit: function () {
    this.meta.isEditing(false);
  },
  init: function (data) {
    this.name(data.name || "");
    this.meta.original(data || {});
  }
});

var Product = function (data) {
  var initial = data || {};
  this.variants = ko.observableArray();
  Entity.call(this, data);
  this.init(initial);
  
    
  if (initial.variants !== null) {
    for (var i = 0; i < initial.variants.length; i++) {

      // NOTE: this is the misterious line. Creating two instances
      //       of 'new Variant', ends up pushing a single instance. 
      this.variants.push(new Variant(initial.variants[i]));
      //-----------------------------------------------------------
        
      alert(this.variants()[i].name());
    }
  }

  this.amIEditing = ko.computed(function () {
    return this.meta.isEditing();
  }, this, {
    deferEvaluation: true
  });
};

var Variant = function (data) {
  var initial = data || {};
  Entity.call(this, data);
  this.init(initial);
  
  this.amIEditing = ko.computed(function () {
    return this.meta.isEditing();
  }, this, {
    deferEvaluation: true
  });
};

Product.prototype = Object.create(Entity.prototype);

Variant.prototype = Object.create(Entity.prototype);

var viewModel = function () {
  var self = this;
  self.product = new Product({
    name: "luigi",
    variants: [{
      name: "first"
    }, {
      name: "second"
    }]
  });
};

ko.applyBindings(new viewModel());