just wondering in knockout is there a way to get the name of the models observables?
in the fiddle https://jsfiddle.net/othkss9s/1/
I have a an observable array that I am using to create a table. and I have an array used to store the table headers. is there a way to just get the table headers from the products object itself and not have to have a separate array to store the table headers? as in just get name, price, and tags from the model itself somehow?
function Product(name, price, tags) {
this.name = ko.observable(name);
this.price = ko.observable(price);
tags = typeof(tags) !== 'undefined' ? tags : [];
this.tags = ko.observableArray(tags);
}
function model() {
var self = this;
this.shoppingCart = ko.observableArray("");
this.headers = ["name", "price", "tags"];
};
var myViewModel = new model();
$(document).ready(function() {
ko.applyBindings(myViewModel);
myViewModel.shoppingCart.push(
new Product("Buns", 1.49, ['Baked goods', 'Hot dogs']),
new Product("Cups", 2.00, ['Paper Products', 'Drinks']),
new Product("Plates", 1.50, ['Paper Products'])
);
});
and here is the html table.
<table>
<thead>
<tr data-bind='foreach: headers'>
<th data-bind='text: $data'></th>
</tr>
</thead>
<tbody data-bind='foreach: shoppingCart'>
<tr>
<td data-bind='text: name'></td>
<td data-bind='text: price'></td>
<td>
<!-- Add a list of tags. -->
<ul data-bind='foreach: tags'>
<li data-bind='text: $data'></li>
</ul>
</td>
</tr>
</tbody>
</table>
This is one way to extract the observable properties
function Product(name, price, tags) {
this.name = ko.observable(name);
this.price = ko.observable(price);
tags = typeof(tags) !== 'undefined' ? tags : [];
this.tags = ko.observableArray(tags);
}
function model() {
var self = this;
this.shoppingCart = ko.observableArray([]);
this.headers = ko.observableArray([]);
};
var myViewModel = new model();
$(document).ready(function() {
ko.applyBindings(myViewModel);
myViewModel.shoppingCart.push(
new Product("Buns", 1.49, ['Baked goods', 'Hot dogs']),
new Product("Cups", 2.00, ['Paper Products', 'Drinks']),
new Product("Plates", 1.50, ['Paper Products'])
);
var product = myViewModel.shoppingCart()[0];
for (var key in product) {
if (product.hasOwnProperty(key)) {
myViewModel.headers.push(key);
}
}
});
https://jsfiddle.net/brianlmerritt/t2f43qov/7/
ps - you might want to change the field names to this.Name etc or just make the first letter of the key capitalized before you push it
Related
I have this small MVC application that makes .ajax call, gets data in .json format and display the response in html. I am new to knockoutJS and I am having a problem mapping the json format into the model.
I am able to map the json data I received upon the .ajax call into an array (categories) but when I try to add it to shoppingCart array it doesn't show the mapped data.
Can some one help me why the data is lost by the time it tries to add it to shoppingCart collection?
Database Tables:
Product
Id | Name | Price|
1 | Buns | 1.00
ProductTag
Id | Name | Fk_Product_Id|
1 | Baked Goods| 1
/* Fk_Product_Id - points to Product Id */
Response in Json format:
{
"Data": [
{
"ProductTags": [
{
"Id": 1,
"Name": "Baked Goods",
"Fk_Product_Id": 1
},
{
"Id": 2,
"Name": "Hot Dogs",
"Fk_Product_Id": 1
}
],
"Id": 1,
"Name": "Buns",
"Price": 1.00
}
],
}
js file:
var categories = [];
$.ajax({
url: 'ShoppingCart/GetAllProducts',
cache: false,
type: 'GET',
contentType: 'application/json; charset=utf-8',
data: {},
success: function (data) {
// I had to map the data to model manually.
// Ko.mapper didn't work for me.
var parsed = JSON.parse(data);
var product = parsed.Data;
console.log(parsed);
for (var i = 0; i < product.length; i++) {
var id = product[i].Id;
var name = product[i].Name;
var price = product[i].Price;
var productTag = product[i].ProductTags;
categories.push(new Product(id, name, price,productTag));
}
}
});
function Product(id, name, price, productTags) {
var self = this;
self.id = ko.observable(id),
self.name = ko.observable(name);
self.price = ko.observable(price);
self.productTags = typeof (productTags) !== "undefined" ? productTags : [];
self.productTags = ko.observableArray(productTags);
}
function PersonViewModel() {
var self = this;
self.firstName = ko.observable("John");
self.lastName = ko.observable("Smith");
self.checkout = function () {
alert("Trying to checkout");
};
self.shoppingCart = ko.observableArray(categories);
};
//var viewModel = new PersonViewModel();
//ko.applyBindings(viewModel);
var viewModel = new PersonViewModel();
ko.applyBindings(viewModel, document.getElementById('shoppingCart'));
Html:
<div id="shoppingCart">
<table>
<thead><tr>
<th>Item number</th>
<th>Product</th>
<th>Price</th>
<th>Tags</th>
<th>Action</th>
</tr></thead>
<tbody data-bind='foreach: shoppingCart'>
<tr>
<td data-bind='text: $index'></td>
<td data-bind='text: name'></td>
<td data-bind='text: price'></td>
<td>
<ul data-bind="foreach: productTags">
<li data-bind="text:$data">
</li>
</ul>
</td>
<td>
<button data-bind='click: $root.removeProduct'>Remove</button>
</td>
</tr>
</tbody>
</table>
</div>
You're just updating a local variable called categories and not the viewmodel's observableArray property. Instead, loop through Data and push to viewModel.shoppingCart.
setTimeout(() => {
const response = {"Data":[{"ProductTags":[{"Id":1,"Name":"Baked Goods","Fk_Product_Id":1},{"Id":2,"Name":"Hot Dogs","Fk_Product_Id":1}],"Id":1,"Name":"Buns","Price":1.00}],}
response.Data.forEach(p => {
// push to observableArray
viewModel.shoppingCart.push(new Product(p.Id, p.Name, p.Price, p.ProductTags))
})
}, 2000)
function Product(id, name, price, productTags) {
var self = this;
self.id = ko.observable(id);
self.name = ko.observable(name);
self.price = ko.observable(price);
let tags = typeof(productTags) !== "undefined" ? productTags : [];
self.productTags = ko.observableArray(tags);
}
function PersonViewModel() {
var self = this;
self.firstName = ko.observable("John");
self.lastName = ko.observable("Smith");
self.shoppingCart = ko.observableArray([]); // initialize to empty array
};
var viewModel = new PersonViewModel();
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<table>
<thead>
<tr>
<th>Item number</th>
<th>Product</th>
<th>Price</th>
<th>Tags</th>
<th>Action</th>
</tr>
</thead>
<tbody data-bind='foreach: shoppingCart'>
<tr>
<td data-bind='text: $index'></td>
<td data-bind='text: name'></td>
<td data-bind='text: price'></td>
<td>
<ul data-bind="foreach: productTags">
<li data-bind="text:Name">
</li>
</ul>
</td>
<td>
<button data-bind='click: $root.removeProduct'>Remove</button>
</td>
</tr>
</tbody>
</table>
I'm trying to generate a table using properties of two viewmodels, which are sub viewmodels of the main viewmodel, which ko.applyBindings() is called using.
The idea is to generate a row for each element in SubVM1 where the first cell is the element's name. Then for every element in SubVM2, an additional cell is added to the row.
The rows generate correctly and the first cell shows the SubVM1 name, but it is only followed by one cell, instead of however many elements are in SubVM2.
Also, the function in the data-bind doesn't work either. I've tried declaring the Value function as a prototype of SubV2 but it errors out as undefined.
Regardless, something I'm not sure about is clearly happening with the binding context, and help would be appreciated.
<tbody data-bind="foreach: {data: SubViewModel1, as: 'SubVM1'}">
<tr>
<td data-bind="text: SubVM1.Name" />
<!-- ko foreach: {data: $root.SubViewModel2, as: 'SubVM2'} -->
<td data-bind="text: Value(SubVM1.Type)"></td>
<!-- /ko -->
</tr>
</tbody>
Edit: Partially done jsfiddle: http://jsfiddle.net/jgr71o8t/1
There are a couple of things that I could see.
The first one is that the <td data-bind='' />.
Knockout does not generally like self closed tags. Always use the closing tag, in this case <td data-bind=''></td>
The second is that anything you want updated on the screen should be an ko.observable or ko.observableArray. any changes to properties after ko.applyBindings will not be reflected on the screen
HTML
<table border="1">
<tbody data-bind="foreach: {data: subViewModel1, as: 'SubVM1'}">
<tr>
<td data-bind="text: name"></td>
<!-- ko foreach: {data: $root.subViewModel2, as: 'SubVM2'} -->
<td data-bind="text: SubVM2.Value(SubVM1.Type)"></td>
<!-- /ko -->
</tr>
</tbody>
</table>
JS Fiddle Demo with knockout bindings on all properties
function MasterViewModel() {
var self = this;
self.subViewModel1 = ko.observableArray([]);
self.subViewModel2 = ko.observableArray([]);
}
function SubVM1ViewModel() {
var self = this;
self.name = ko.observable("Sub View Model 1");
self.otherProperty = ko.observable(43);
}
function SubVM2ViewModel() {
var self = this;
self.title = ko.observable('Sub View Model 2');
self.subVM1List = ko.observableArray([]);
}
SubVM2ViewModel.prototype.Value = function (type) {
for (var i = 0; i < this.subVM1List().length; i++) {
if (this.subVM1List()[i].Type === type) {
return this.subVM1List()[i].name();
}
}
};
var masterVM = new MasterViewModel();
var subVM2 = new SubVM2ViewModel();
subVM2.subVM1List.push(new SubVM1ViewModel());
masterVM.subViewModel1.push(new SubVM1ViewModel());
masterVM.subViewModel2.push(subVM2);
ko.applyBindings(masterVM);
JS Fiddle Demo with straight javascript properties
function MasterViewModel() {
var self = this;
self.subViewModel1 = [];
self.subViewModel2 = [];
}
function SubVM1ViewModel() {
var self = this;
self.name = "Sub View Model 1";
self.otherProperty =43;
}
function SubVM2ViewModel() {
var self = this;
self.title = 'Sub View Model 2';
self.subVM1List = [];
}
SubVM2ViewModel.prototype.Value = function (type) {
for (var i = 0; i < this.subVM1List.length; i++) {
if (this.subVM1List[i].Type === type) {
return this.subVM1List[i].name;
}
}
};
var masterVM = new MasterViewModel();
var subVM2 = new SubVM2ViewModel();
subVM2.subVM1List.push(new SubVM1ViewModel());
masterVM.subViewModel1.push(new SubVM1ViewModel());
masterVM.subViewModel2.push(subVM2);
ko.applyBindings(masterVM);
I can't understand why these Knockout table bindings aren't working:
Javascript:
$(function () {
var FileObject = function(id, name) {
this.id = id;
this.name = name;
};
var FilesModel = function() {
this.filesSelected = ko.observable(false);
this.myFiles = ko.observableArray([new FileObject(1, 'test_1')]);
this.myFiles.push(new FileObject(2, 'test_2'));
};
var filesModel = new FilesModel();
window.filesModel = filesModel;
ko.applyBindings(filesModel);
filesModel.myFiles().push(new FileObject(3, 'test_3')); // This never shows
alert(filesModel.myFiles().length); // Shows 3 items
});
HTML:
<h3>TABLE 1</h3>
<table>
<tbody data-bind="foreach: myFiles">
<tr>
<td>FILE:</td>
<td data-bind="text: name"></td>
</tr>
</tbody>
</table>
<h3>TABLE 2</h3>
<table>
<tbody data-bind="foreach: myFiles()">
<tr>
<td>FILE:</td>
<td data-bind="text: name"></td>
</tr>
</tbody>
</table>
In both of these tables, the first 2 files will show, but the 3rd file doesn't. What am I missing?
You're really close. Two main things to point out:
Status isn't an observable, and you're attempting to unwrap it with text: status().
You're pushing the new FileObject into an unwrapped array, meaning you're bypassing the observable altogether. Push new items directly into the observable array, you'll have better luck.
I've put together a jsbin example based on your original source.
Specifically, this:
filesModel.myFiles().push(new FileObject(3, 'test_3')); // This never shows
Should be:
filesModel.myFiles.push(new FileObject(3, 'test_3')); // Now it does
In your HTML, you were trying to data-bind status(), but status is not an observable. One approach is to make your FileObject members observables.
Also, your third FileObject was never showing because your syntax was wrong. Instead of filesModel.myFiles().push, it should be just filesModel.myFiles.push
See updated fiddle
$(function () {
var FileObject = function(id, name, size, status, progress) {
this.id = ko.observable(id);
this.name = ko.observable(name);
this.status = ko.observable(status);
};
var FilesModel = function() {
this.filesSelected = ko.observable(false);
this.myFiles = ko.observableArray([new FileObject(1, 'test_1')]);
this.myFiles.push(new FileObject(2, 'test_2', 3, 'status'));
};
var filesModel = new FilesModel();
window.filesModel = filesModel;
ko.applyBindings(filesModel);
filesModel.myFiles.push(new FileObject(3, 'test_3')); // This never shows
alert(filesModel.myFiles().length); // Shows 3 items
});
I have a structure of an array with a nested array. I am trying to remove an item from the nested array but I get the error that "remove is not a function".
I've recreated the problem in a simple jsFiddle - http://jsfiddle.net/rswailes/gts5g/ and also pasted the code below.
It's possible that the way I have set up the observables is not correct, but I'm stumped.
This is my html:
<script id="bookGroupTemplate" type="text/html">
<br/>
<h3><span data-bind="text: group_name"></span></h3>
<table>
<thead>
<tr>
<th>Author</th>
<th>Title</th>
<th>Genre</th>
<th></th>
</tr>
</thead>
<tbody data-bind='template: {name: "bookRowTemplate", foreach: books}'></tbody>
</table>
</script>
<script id="bookRowTemplate" type="text/html">
<tr>
<td data-bind="text: author"></td>
<td data-bind="text: title"></td>
<td data-bind="text: genre"></td>
</tr>
</script>
<h1>Books!</h1>
<div data-bind='template: {name: "bookGroupTemplate", foreach: bookGroups}'></div>
<br/><br/>
<button data-bind="click: function(){viewModel.handleButtonClick(); }">Move One From Now to Later</button>
This is the javascript:
var BookGroup = function(group_name, booksToAdd){
var self = this;
this.group_name = ko.observable(group_name);
this.books = ko.observableArray();
_.each(booksToAdd, function(book){
self.books.push(ko.observable(book));
});
}
var Book = function(author, title, genre) {
this.author = ko.observable(author);
this.title = ko.observable(title);
this.genre = ko.observable(genre);
}
var PageViewModel = function() {
var self = this;
this.bookGroups = ko.observableArray();
this.bookToUse = new Book("Robin Hobb", "Golden Fool", "Fantasy");
this.indexAction = function() {
var groups = [];
var booksArray = [];
booksArray.push(this.bookToUse);
booksArray.push(new Book("Patrick R Something", "Name Of The Wind", "Fantasy"));
booksArray.push(new Book("Someone Else", "Game Of Thrones", "Fantasy"));
groups.push(new BookGroup("To Read Now", booksArray));
booksArray = [];
booksArray.push(new Book("Terry Pratchett", "Color of Magic", "Discworld"));
booksArray.push(new Book("Terry Pratchett", "Mort", "Discworld"));
booksArray.push(new Book("Terry Pratchett", "Small Gods", "Discworld"));
groups.push(new BookGroup("To Read Later", booksArray));
this.bookGroups(groups);
};
this.handleButtonClick = function(){
console.log(this.bookGroups()[0].books().length);
this.bookGroups()[0].books().remove(this.bookToUse);
};
};
viewModel = new PageViewModel();
ko.applyBindings(viewModel);
viewModel.indexAction();
Why is remove not recognized here? Is this the right way to construct the model?
Many thanks for any advice :)
There were 2 errors:
You tried to call remove function form javascript array instead observable array.
You don't need to wrap book object with observable when put it to observableArray.
Have you tried remove on the observableArray and not on its contents, in other words :
this.handleButtonClick = function(){
console.log(this.bookGroups()[0].books().length);
this.bookGroups()[0].books.remove(this.bookToUse);
};
see Observable Arrays
I'm currently trying to extend the KnockoutJS shopping cart example to preload existing rows from a JSON collection.
Say, I have an object like this:
var existingRows = [{
"Category":Classic Cars,
"Product":2002 Chevy Corvette,
"Quantity":1,
}, {
"Category":Ships,
"Product":Pont Yacht,
"Quantity":2,
}];
I am trying to modify the example so that on load it populates the grid with two rows, with the comboboxes pre-set to the items in the JSON object.
I can't seem to get this object playing nicely with JSFiddle, but I've got as far as modifying the Cart and CartLine functions, and ApplyBindings call as follows:
var CartLine = function(category, product) {
var self = this;
self.category = ko.observable(category);
self.product = ko.observable(product);
// other code
}
var Cart = function(data) {
var self = this;
self.lines = ko.observableArray(ko.utils.arrayMap(data, function(row) { return new CartLine(row.Category, row.Product);}))
// other code
}
ko.applyBindings(new Cart(existingRows));
This correctly inserts two rows on load, but does not set the drop down lists. Any help would be much appreciated :)
The problem is that the values of the category and product observables in the CartLine object are not simple strings. They are actual objects, e.g. category refers to a specific category from the sample data that's provided in that example, same with product.
But you're just setting them to strings.
(Another problem is that your JS object existingRows is not valid javascript because of quotes missing around the string)
To get that example working with your existingRows object you could extract the relevant category and product from the sample data:
var Cart = function(data) {
// Stores an array of lines, and from these, can work out the grandTotal
var self = this;
self.lines = ko.observableArray(ko.utils.arrayMap(data, function(row) {
var rowCategory = ko.utils.arrayFirst(sampleProductCategories, function(category) {
return category.name == row.Category;
});
var rowProduct = ko.utils.arrayFirst(rowCategory.products, function(product) {
return product.name == row.Product;
});
return new CartLine(rowCategory, rowProduct, row.Quantity);
}));
// other code
}
Updated fiddle: http://jsfiddle.net/antishok/adNuR/664/
<h1> Online shopping</h1>
<button id="btnAdd" data-bind='click: addLine'>Add product</button><br /><br />
<table width='100%'>
<thead>
<tr>
<th width='25%'>Product</th>
<th class='price' width='15%'>Price</th>
<th class='quantity' width='10%'>Quantity</th>
<th class='price' width='15%'>Subtotal (in rupees)</th>
<th width='10%'> </th>
</tr>
</thead>
<tbody data-bind='foreach: items'>
<tr>
<td>
<select data-bind='options: products, optionsText: "name", optionsCaption: "Select...", value: product'> </select>
</td>
<td class='price' data-bind='with: product'>
<span data-bind='text: (price)'> </span>
</td>
<td class='quantity'>
<input data-bind='visible:product, value: quantity, valueUpdate: "afterkeydown"' />
</td>
<td class='price'>
<span data-bind='visible: product, text: subtotal()' > </span>
</td>
<td>
<a href='#' data-bind='click: $parent.removeLine'>Remove</a>
</td>
</tr>
</tbody>
</table>
<h2>
Total value: <span data-bind='text: grandTotal()'></span> rupees
</h2>
$(document).ready(function () {
$("#btnAdd").button();
ko.applyBindings(new OnlineShopping());
});
function formatCurrency(value) {
return "$" + value.toFixed(2);
}
var Item = function () {
var self = this;
self.product = ko.observable();
self.quantity = ko.observable(1);
self.subtotal = ko.computed(function () {
var result = self.product() ? self.product().price * parseInt("0"+self.quantity(), 10) : 0;
return result;
});
};
var OnlineShopping = function () {
var self = this;
// List of items
self.items = ko.observableArray([new Item()]);
// Compute total prize.
self.grandTotal = ko.computed(function () {
var total = 0;
$.each(self.items(), function () { total += this.subtotal() })
return total;
});
// Add item
self.addLine = function () {
self.items.push(new Item())
};
// Remove item
self.removeLine = function () {
self.items.remove(this)
};
};
// Item collection
var products = [{ name: "IPhone", price: "45000" }, { name: "Galaxy Y", price: "7448" }, { name: "IPad", price: "25000" }, { name: "Laptop", price: "35000" }, { name: "Calci", price: "750"}];