So the thing you wanted us to help you with required quite a bit of time to build, but was actually extremely simple (To be fair, there was enough info in most basic KO tutorials to do all this.)
So I've built one page and one MVC controller with 3 methods: One for the page itself and two for GETting or POSTing data.
Here is the controller's code:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public JsonResult PostDriversModel(DriversModel model)
{
return Json(new { Success = true }, JsonRequestBehavior.AllowGet);
}
[HttpGet]
public JsonResult GetDriversModel()
{
var model = new DriversModel
{
Drivers = new List<Driver>
{
new Driver
{
FullName = "John Doe",
Cars = new List<Car>
{
new Car {Code = "car0", Name = "Amazing car"},
new Car {Code = "car1", Name = "Cool car"}
},
},
new Driver
{
FullName = "Johnny Dough",
Cars = new List<Car>
{
new Car {Code = "car2", Name = "Another Amazing car"}, new Car {Code = "car3", Name = "Another Cool car"}
}
},
}
};
return Json(model, JsonRequestBehavior.AllowGet);
}
}
As you can see, the controller is very barebones and largest method of them all is GetDriversModel() which priovides the page with sample data to work with.
Here you would probably do something like querying your long-term storage for a tree to render on a client side. It would most likely be marked with an Id of some sort, but since those details were not in your question, I've ommitted them. You can easily figure it out with this example.
The most interesting part is actually on the page where I've used knockout to build a renderer for the DriversModel data structure. First, lets check the JavaScript:
In the heart of a KO view is a ViewModel:
function DriversViewModel() {
var self = this;
self.Drivers = ko.observableArray([]);
self.addDriver = function() {
self.Drivers.push(new DriverModel({ FullName: 'Mr. Noname', Cars: [] }));
};
self.removeDriver = function(driver) {
self.Drivers.remove(driver);
};
self.update = function() {
$.ajax("/Home/PostDriversModel", {
data: ko.toJSON({ Drivers: self.Drivers }),
type: "post", contentType: "application/json",
success: function () { alert('Success!'); }
});
}
$.getJSON('/Home/GetDriversModel', function (data) {
var drivers = data.Drivers.map(function (driver) { return new DriverModel(driver); });
drivers.push(new DriverModel({ Cars: [], FullName: 'Mr Nocars' }));
self.Drivers(drivers);
});
}
In it we define a couple of methods for adding/removing the drivers from the tree as well as the method to post contents back to the server. The ViewModel is pretty straight forward (as pretty much everything in this example). Note that I've added another random driver to the list right after JS ahs done querying the server for data. This is done just for the fun of it (I was experimenting as well).
Here are ViewModels for the remainder of Entities:
function CarModel(data) {
var self = this;
self.Code = ko.observable(data.Code);
self.Name = ko.observable(data.Name);
}
function DriverModel(data) {
var self = this;
self.addCar = function () {
self.Cars.push(new CarModel({ Name: 'Tank', Code: '__' }));
};
self.removeCar = function (car) {
self.Cars.remove(car);
};
self.Cars = ko.observableArray(data.Cars.map(function(car) { return new CarModel(car); }));
self.FullName = ko.observable(data.FullName);
}
As you can see all of those have some initialization logic inside them where we map the JSON objects that we got from our server to our client-side abstractions. You can also notice that they don't follow naming conventions for JavaScript objects. I've done this deliberately so that MVC has no problem when mapping them to C# object when we are done working with them on the client side.
This might be not a very good thing to do in a long term but it will do for the sake of simplicity.
So what basically happens is when our DriversViewModel has requested items from server it maps all of the data to Knockout-friendly abstractions which are being tracked by the Knockout as they get changed. All that remains is to actually tell Knockout to use this ViewModel:
ko.applyBindings(new DriversViewModel());
Now knockout is ready to work with those objects and it's time for us to build the UI part.
The page that uses those KO bindings looks like that:
<div>
<a href="#" data-bind="click: $root.addDriver">Add Driver</a>
<a href="#" data-bind="click: $root.update">Update</a>
</div>
<ul data-bind="foreach: Drivers, visible: Drivers().length > 0">
<ul>
<div>
<input data-bind="value: FullName"/>
<a href="#" data-bind="click: $parent.removeDriver">Delete</a>
<a href="#" data-bind="click: addCar">Add Car</a>
</div>
<ul class="no-cars" data-bind="visible: Cars().length == 0">No cars D:</ul>
<ul data-bind="foreach: Cars, visible: Cars().length > 0">
<li>
<div>
<a href="#" data-bind="click: $parent.removeCar">Delete</a>
<label>Car Name:</label> <input data-bind="value: Name"/>
<label>Car Code:</label> <input data-bind="value: Code"/>
</div>
</li>
</ul>
</ul>
</ul>
As you can see there is nothing tricky about it. The most tricky part about setting this all up is to know which data-binding directives to use and to keep track of which ViewModel you are actually using in which context. (This is important when you are using different adding/removal functions defined on different ViewModels.)
And that's it. Here is a screenshot of the resulting solution for a good measure:

And while it looks pretty messy, it gets the job done.
When you click respective Add buttons, cars or drivers get added.
Clicking an Update button will assemble the entire tree and post it back to server, where it is getting converted to POCO with the power of ASP.NET MVC magic.
Here are pastebins of the code so that you can just copy-paste and see it for yourself. You'll have to fiddle with MVC project a bit but I believe that you can handle that.
Page:
http://pastebin.com/2aGkEHEN
Controller:
http://pastebin.com/nZaufcpw
One important note:
If you really wanted to do something like this, you might want to track how user changes the data rather then just get the entire changed structure and overwrite the old data.
Instead of the naiive approach I've shown you in the example, I would try and track the changes that user does to the tree and then send some kind of changeset to the server instead of the whole structure. This way you can apply the changeset in a transaction and achieve the same result but with less bandwidth and more consistency.