Reputation: 5001
Access external observableArray to change one of its properties.
There is two lists in my application. One of these lists consists in store items that I have added to it — very similar to a shopping cart; the other list is to available products to buy. Each product has an "add button" and when I click on it, like a magic, its appears on the "shopping cart".
To remove this product from shopping cart we must to click in the same button that adds our product, because its state is changed to "remove". Or, we can click next to item that is added on list — there is a "remove button" there.
Until here, all works fine. The problem is: when I remove the product by clicking in the "x" of shopping cart, the button of the product doesn't back to normal. In other words, the button doesn't back to "add button".
To see an illustrative example, just click here to go to jsFiddle.
I need to access self.products = ko.observableArray(products);
that is on ProductViewModel
from SummaryViewModel
.
If something happens with jsFiddle, the code is the following.
HTML:
<ul class="summary">
<!-- ko foreach: Summary.items -->
<p data-bind="text: name"></p>
<button class="btn btn-danger btn-mini remove-item">
<i class="icon-remove">×</i>
</button>
<!-- /ko -->
</ul>
<h1>What would you to buy?</h1>
<ul class="products">
<!-- ko foreach: Product.products -->
<li>
<h3 data-bind="text: name"></h3>
<p data-bind="text: desc"></p>
<!-- ko if:isAdded -->
<button data-bind="if: isAdded" class="btn btn-small btn-success action remove">
<i data-bind="click: $root.Summary.remove" class="icon-ok">Remove</i>
</button>
<!-- /ko -->
<!-- ko ifnot:isAdded -->
<form data-bind="submit: function() { $root.Summary.add($data); }">
<button data-bind="ifnot: isAdded" class="btn btn-small action add">
<i class="icon-plus">Add</i>
</button>
</form>
<!-- /ko -->
</li>
<!-- /ko -->
</ul>
JavaScript:
function Product(id, name, desc) {
var self = this;
self.id = ko.observable(id);
self.name = ko.observable(name);
self.desc = ko.observable(desc);
self.isAdded = ko.observable(false);
}
function Item(id, name) {
var self = this;
self.id = ko.observable(id);
self.name = ko.observable(name);
}
function SummaryViewModel() {
var self = this;
self.items = ko.observableArray([]);
self.add = function (item) {
self.items.push(new Item(item.id(), item.name()));
console.log(item);
item.isAdded(true);
};
self.remove = function (item) {
var i = self.items().filter(function(elem){
return elem.id() === item.id();
})[0];
self.items.remove(i);
item.isAdded(false);
};
};
function ProductViewModel(products) {
var self = this;
self.products = ko.observableArray(products);
};
var products = [
new Product(1, "GTA V", "by Rockstar"),
new Product(2, "Watch_Dogs", "by Ubisoft")
];
ViewModel = {
Summary: new SummaryViewModel(),
Product: new ProductViewModel(products)
}
ko.applyBindings(ViewModel);
Upvotes: 0
Views: 158
Reputation: 338208
It's best not to track the same information about an object in two different places. This will always result in syncing issues.
In your case you track whether an object is "added"...
isAdded
observable in your Product model) and items
observable in your Summary model)It's useful to lose one the two places.
For example you could remove the manual list and just keep track of object state in the Product model.
Then you could use a computed observable that returns a filtered view (ko.utils.arrayFilter
) of the selected products and Knockout does all the rest.
function Product(id, name, desc) {
var self = this;
self.id = ko.observable(id);
self.name = ko.observable(name);
self.desc = ko.observable(desc);
self.isAdded = ko.observable(false);
self.addRemoveText = ko.computed(function () {
return self.isAdded() ? "Remove" : "Add";
});
self.addRemove = function () {
self.isAdded(!self.isAdded());
};
}
function SummaryViewModel(parent) {
var self = this;
self.items = ko.computed(function () {
var products = ko.utils.unwrapObservable(parent.products);
return ko.utils.arrayFilter(products, function (product) {
return product.isAdded();
});
});
}
function ProductViewModel(parent) {
var self = this;
self.items = ko.observableArray(parent.products);
}
function ViewModel(products) {
var self = this;
self.products = ko.utils.arrayMap(products, function (init) {
return new Product(init.id, init.name, init.desc);
});
self.Summary = new SummaryViewModel(self);
self.Product = new ProductViewModel(self);
}
See it live here: http://jsfiddle.net/Tomalak/Jr3Tk/4/
Also note that the base HTML got a lot simpler.
<ul class="summary" data-bind="with: Summary">
<!-- ko foreach: items -->
<li>
<p data-bind="text: name"></p>
<button class="btn btn-danger btn-mini remove-item" data-bind="click: addRemove">
<i class="icon-remove">×</i>
</button>
</li>
<!-- /ko -->
</ul>
<h1>What would you to buy?</h1>
<ul class="products" data-bind="with: Product">
<!-- ko foreach: items -->
<li>
<h3 data-bind="text: name"></h3>
<p data-bind="text: desc"></p>
<button class="btn btn-small btn-success action remove">
<i data-bind="click: addRemove, text: addRemoveText" class="icon-ok">Remove</i>
</button>
</li>
<!-- /ko -->
</ul>
Upvotes: 1
Reputation: 14995
The reason this happens is because when you push the item into the summary list it no longer has the isAdded or desc properties. You either need to add these to your summary model or pass the item directly through, and not create a new object.
JavaScript throws the error ('has no property 'isAdded') is how I knew this. Open your console when you are running it to see the error.
Adding console.log(item); showed me the properties that it had.
self.remove = function (item) {
var i = self.items().filter(function(elem){
return elem.id() === item.id();
})[0];
self.items.remove(i);
console.log(item);
item.isAdded(false);
};
Give me a second and I will update your fiddle.
That is a working example. I removed some redundancy (such as going through and filtering for the product when you already have it)
Upvotes: 1
Reputation: 3294
ProductViewModel.products.subscribe(function(products){
SummaryViewModel.products(products);
});
But first you need to add self.products = ko.observableArray([]);
in SummaryViewModel
Did I understand correctly?
Upvotes: 0