JSFiddle - React, Tailwind, and code Playground
HTML
<div id="root">
<div data-bind="with: customer">
<ul>
<li>Name: <input data-bind="textInput: name"></li>
<li>E-Mail: <input data-bind="textInput: email"></li>
<li>Phone: <input data-bind="textInput: phone"></li>
<li>Parts:
<ul data-bind="foreach: parts">
<li>Part No: <input data-bind="textInput: partNumber"></li>
<li>Quantity: <input data-bind="textInput: qty"></li>
<li>Notes: <input data-bind="textInput: notes"></li>
<li><button data-bind="click: $root.removePartAction($data)">Remove</button></li>
</ul>
<button data-bind="click: $root.addPartAction()">Add</button>
</li>
</ul>
</div>
<div>
<pre data-bind="text: emailContent"></pre>
</div>
<div>
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>
</div>
</div>
JavaScript
class ViewModel {
constructor() {
this.customer = ko.observable(new Customer());
this.emailContent = ko.computed(this._emailContent, this);
}
addPartAction() {
return () => this.customer().addPart(new Part());
}
removePartAction(p) {
return () => this.customer().removePart(p);
}
_emailContent() {
const {name,email,phone,parts} = this.customer();
return `Name: ${name()}
Email: ${email()}
Phone: ${phone()}
Parts:
${ko.utils.arrayMap(parts(), ({partNumber,qty,notes}, i) =>
`${i+1}.\tPartNumber: ${partNumber()} -- Qty: ${qty()} -- Notes: ${notes()}`
).join('\n ')}
`;
}
}
class Customer {
constructor() {
this.name = ko.observable();
this.email = ko.observable();
this.phone = ko.observable();
this.parts = ko.observableArray();
}
addPart(p) {
this.parts.push(p);
}
removePart(p) {
ko.utils.arrayRemoveItem(this.parts(), p);
this.parts.valueHasMutated();
}
}
class Part {
constructor() {
this.partNumber = ko.observable();
this.qty = ko.observable();
this.notes = ko.observable();
}
}
const viewModel = new ViewModel();
ko.applyBindings(viewModel, document.querySelector('#root'));