I really liked the live example in cartEditor. This is what I need. Well, almost what if I want to add another dropdown. For example if I want to add to an existing example another field country
. The scenario is the following: I select the country, vehicle category and according to this choose the vehicle itself. How to bind two dropdowns to a third?
Tables in the database about such:
Table vehicle
id name countryId categoryId
Table category
id name
Table country
id name
File for a live example is located at knockoutjs.com
You'd continue with the same principle:
- Define your data in an array or plain object
- Construct your viewmodels that make data renderable and selectable
- Create
ko.pureComputed
properties that select subsets of your data based on user input.
For example, let's say your products have both a category
and a country
:
var products = [
{
name: "Cheese",
country: "The Netherlands",
category: "Dairy"
}
]
Now, if you have two observables, bound to your UI:
this.selectedCountry = ko.observable();
this.selectedCategory = ko.observable();
You can create a ko.pureComputed
that makes a selection of products that meet the requirements:
this.selectedProducts = ko.pureComputed(function() {
return products.filter(function(product) {
return product.category === this.selectedCategory() &&
product.country === this.selectedCountry();
});
}, this);
Example:
var products = [
{
name: "Gouda Cheese",
country: "The Netherlands",
category: "Dairy"
},
{
name: "Camambert",
country: "France",
category: "Dairy"
},
{
name: "Red Wine",
country: "France",
category: "Alcoholic beverages"
}
];
var ViewModel = function() {
// These will be bound to your dropdowns' selections
this.selectedCountry = ko.observable();
this.selectedCategory = ko.observable();
// This computed calculates which products from your data
// meet the requirements whenever one of the selections changes
this.selectedProducts = ko.pureComputed(function() {
return products.filter(function(product) {
return product.category === this.selectedCategory() &&
product.country === this.selectedCountry();
}.bind(this));
}, this);
// Here, we create a list of countries that appear in your data,
// this list is used to fill the dropdown's options
this.countries = getKeyUniques(products, "country");
// Do the same for categories
this.categories = getKeyUniques(products, "category");
};
ko.applyBindings(new ViewModel());
// Utils
function getKeyUniques(arr, key) {
return Object.keys(arr.reduce(function(map, item) {
map[item[key]] = true;
return map
}, {}));
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="options: countries, value: selectedCountry"></select>
<select data-bind="options: categories, value: selectedCategory"></select>
<ul data-bind="foreach: selectedProducts">
<li data-bind="text: name"></li>
</ul>
My solution is as follows. I wanted to filter based on ID. Plan to get the lists of countries and categories from the server and then receive from the server a filtered list of products. A huge thank you to the user. With Your help and with the help of the example on the website http://knockoutjs.com/examples/cartEditor.html I solved my problem.
Post here my solution:
var countries = [
{
"id": 1,
"name": "Russia"
},
{
"id": 2,
"name": "USA"
},
{
"id": 3,
"name": "Great Britain"
}
];
var categories = [
{
"id": 1,
"name": "Classic Cars"
},
{
"id": 2,
"name": "Motorcycles"
},
{
"id": 3,
"name": "Planes"
}
];
var products = [
{
"name": "P-51-D Mustang",
"countryId": 2,
"categoryId": 3,
"price": 12.42
},
{
"name": "1997 BMW R 1100 S",
"countryId": 2,
"categoryId": 2,
"price": 60.87
},
{
"name": "2002 Chevy Corvette",
"countryId": 2,
"categoryId": 1,
"price": 100.87
},
{
"name": "1998 Chrysler Plymouth Prowler",
"countryId": 1,
"categoryId": 1,
"price": 150.87
}
];
var CartLine = function () {
var self = this;
self.countries = ko.observableArray(countries);
self.categories = ko.observableArray(categories);
self.quantity = ko.observable(1);
self.selectedCountry = ko.observable();
self.selectedCategory = ko.observable();
self.selectedProduct = ko.observable();
self.subtotal = ko.pureComputed(function () {
return self.selectedProduct() ? (self.selectedProduct().price * self.quantity()) : "yet";
});
self.products = ko.pureComputed(function () {
return $.grep(products, function (item) {
return (self.selectedCountry() && self.selectedCategory() && item.countryId === self.selectedCountry().id && item.categoryId === self.selectedCategory().id);
});
});
};
var Cart = function () {
var self = this;
self.lines = ko.observableArray([new CartLine()]);
self.lines2 = ko.observableArray([new CartLine()]);
self.grandTotal = ko.pureComputed(function () {
var total = 0;
$.each(self.lines(), function () {
total += this.subtotal();
});
return total;
});
// Operations
self.addLine = function () {
self.lines.push(new CartLine());
};
self.removeLine = function (line) {
self.lines.remove(line);
};
};
ko.applyBindings(new Cart());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!DOCTYPE html>
<html>
<head>
<title>TODO supply a title</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="stl.css" />
</head>
<body>
<div class='liveExample'>
<table width='50%'>
<thead>
<tr>
<th width='25%'>Country</th>
<th width='25%'>Category</th>
<th class='price' width='15%'>Product</th>
<th class='price' width='15%'>Price</th>
<th class='quantity' width='10%'>Quantity</th>
<th class='price' width='15%'>Subtotal</th>
<th width='10%'></th>
</tr>
</thead>
<tbody data-bind='foreach: lines'>
<tr>
<td>
<select data-bind='options: countries, optionsText: "name", optionsCaption: "Select...", value: selectedCountry'> </select>
</td>
<td>
<select data-bind='options: categories, optionsText: "name", optionsCaption: "Select...", value: selectedCategory'> </select>
</td>
<td>
<select data-bind='visible: (selectedCountry() && selectedCategory()), options: products, optionsText: "name", optionsCaption: "Select...", value: selectedProduct'> </select>
</td>
<td>
<span data-bind='text: selectedProduct() ? selectedProduct().price : 0'> </span>
</td>
<td class='quantity'>
<input data-bind='value: quantity, valueUpdate: "afterkeydown"' />
</td>
<td class='price'>
<span data-bind='text: subtotal()'> </span>
</td>
<td>
<a href='#' data-bind='click: $root.removeLine'>Remove</a>
</td>
</tr>
</tbody>
</table>
<p class='grandTotal'>
Total value: <span data-bind='text: grandTotal()'> </span>
</p>
<button data-bind='click: addLine'>Add product</button>
</div>
<script src="knockout-3.4.1.js" type="text/javascript"></script>
<script src="jquery-3.1.1.min.js" type="text/javascript"></script>
<script src="app.js" type="text/javascript"></script>
</body>
</html>
I did what you asked for based on the approach below -
- Keeping the track of the values selected in the dropdowns for category and
country
and filtering
out the vehicles array on the basis of countryId
and categoryId
For this, I have subscribed
to the selected values of country
and category
, so when either of them changes, I call the filters to update the selected vehicle so that it is updated in the third dropdown.
It also includes some test results at the end of the view model definition.
P.S. - Spare me for the bad combination of data, but I guess my efforts would be enough for you to get the gist of it ;-)
Here is the fiddle