Reputation: 12745
I have an ObservavleArray , which is initialized with two items when the UI loads.
Now there is an add button which pushes new observable item to observable array. This works fine.
But when I edit the values on text box and I expect them to change on underlying observable item, but this does not happen.
Here is the code snippet for that.
var InventoryViewModel = function() {
var self = this;
self.STockItems = ko.observableArray();
InitializeStock(self);
self.addItems = function(vm) {
self.STockItems.push(new ko.observable(''));
}
self.ProcessInventory = function(vm) {
console.log(vm.STockItems());
};
}
var vm = new InventoryViewModel();
ko.applyBindings(vm);
function InitializeStock(context) {
context.STockItems.push(new ko.observable('Item1'));
context.STockItems.push(new ko.observable('Item2'));
}
li {
list-style: none;
margin-top: 10px;
}
.info-text {
margin-top: 30px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<ul data-bind="foreach: STockItems">
<li>
<span class="input-group">
<input data-bind="textInput: $data" />
</span>
</li>
</ul>
<div class="text-right"> <a data-bind="click:addItems">+ Add more</a></div>
<button type="submit" class="btn btn-accent" data-bind="click: $root.ProcessInventory">Process Inventory</button>
<pre class='info-text' data-bind="text: ko.toJSON($data, null, '\t')">
</pre>
http://jsfiddle.net/xvza6nu2/1/
Edit: As I am pushing a observable item directly , I guess I do not have to create a wrapper to push the items.
function EditableField(initialValue) {
this.value = ko.observable(initialValue);
}
If I must use this wrapper, then why? What's the difference?
Upvotes: 0
Views: 1659
Reputation: 3959
Observables need to be bound to a property in the viewmodel, that's the only way to reference them from the HTML.
$data is a reference to the viewmodel's context rather than the observable itself. It just so happened that in your foreach
context there was only 1 observable, so it looked like your initial setup was working.
Read all about the binding context here.
To get it to work properly, you need to push objects into your observableArray, with named properties that will later be referenced from the HTML. Each object in the observableArray becomes a mini-viewmodel in the foreach
loop.
Edit:
new
to create on observable.$root
refers to the primary viewModel. It's useful to use it when we want to reference it from a child's/descendant's viewModel. In your case, for ProcessInventory
click, it's not needed since we are already in the primary viewModel context. var InventoryViewModel = function() {
var self = this;
self.STockItems = ko.observableArray();
InitializeStock(self);
self.addItems = function(vm) {
self.STockItems.push({item : ko.observable('')});
}
self.ProcessInventory = function(vm) {
console.log(vm.STockItems());
};
}
var vm = new InventoryViewModel();
ko.applyBindings(vm);
function InitializeStock(context) {
context.STockItems.push({item : ko.observable('Item1')});
context.STockItems.push({item : ko.observable('Item2')});
}
li {
list-style: none;
margin-top: 10px;
}
.info-text {
margin-top: 30px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<ul data-bind="foreach: STockItems">
<li>
<span class="input-group">
<input data-bind="textInput: item" />
</span>
</li>
</ul>
<div class="text-right"> <a data-bind="click:addItems">+ Add more</a></div>
<button type="submit" class="btn btn-accent" data-bind="click: ProcessInventory">Process Inventory</button>
<pre class='info-text' data-bind="text: ko.toJSON($data, null, '\t')">
</pre>
Upvotes: 1