Reputation: 1382
I cannot figure out why the table cell in the view is not updating when i update the viewmodel in the js/knockout code.
Here is my fiddle I got working with the help of @haim770 and @Matt.kaaj. So when you tab out of the last cell in the last row. Then you type something into the first cell on the new row and tab out, and it is supposed to update the description. I checked in the console and it is correctly updating the viewmodel. When googling it, I found I had to make my observable array include observables in it as well. So i think I am doing that correctly using the mapping plugin. Here is all my code from the fiddle.
HTML:
<table class="table table-bordered table-striped" id="brochureItems">
<thead>
<tr>
<th>
Item No.
</th>
<th>
Broc Code
</th>
<th width="40%">
Item Desc
</th>
<th>
Retail
</th>
<th>
Remove
</th>
</tr>
</thead>
<tbody data-bind="foreach: items">
<tr>
<td>
<input data-bind="value: itemNo, hasFocus: true, event: { blur: $parent.checkItemNo }" class="form-control item-ID" />
</td>
<td>
<div data-bind="if: ($index() < ($parent.items().length - 1))"><input data-bind="value: brocCode" class="form-control" readonly="readonly" /></div>
<div data-bind="if: ($index() === ($parent.items().length - 1))"><input data-bind="value: brocCode" class="form-control" /></div>
</td>
<td class="item-desc">
<input data-bind="value: itemDesc" class="form-control" tabindex="-1" />
</td>
<td class="item-retail">
<div data-bind="if: ($index() === ($parent.items().length - 1))"><input data-bind="value: retail, valueUpdate: 'afterkeydown', enterPress: 'addRow'" class="form-control" /></div>
<div data-bind="if: ($index() < ($parent.items().length - 1))"><input data-bind="value: retail, valueUpdate: 'afterkeydown'" class="form-control" /></div>
</td>
<td class="remove"><span class="glyphicon glyphicon-remove removeRow" data-bind="click: $parent.removeItem"></span></td>
</tr>
</tbody>
JS/Knockout:
var itemsModel = function(items) {
var self = this;
//console.log(JSON.stringify(items));
//self.items = ko.observableArray();
//ko.mapping.fromJS(items,{},self.items)
//self.items = ko.mapping.fromJSON(items);
//self.items = ko.observableArray(items);
self.items = ko.mapping.fromJSON(items);
self.checkItemNo = function(data) {
console.log("blurred!");
//data = ko.observable(data);
//itemModel(data.itemNo)
//var itemNo = $.trim(data.itemNo);
var itemNo = "abc";
if (itemNo != "") {
var item = "";
/*
$.each(fullItemList, function(i, v) {
if (v.No.search(itemNo) != -1) {
item = v.Description;
return;
}
});
if(item != "") {
console.log("found: " + item);
var match = ko.utils.arrayFirst(self.items, function(item) {
return itemNo === item.itemNo;
});
*/
var match = false;
item = "Mudguard front";
if (!match) {
console.log("not a match!");
console.log(data);
data.itemDesc = item;
}
}
}
self.addLine = function() {
self.items.push(
{
itemNo: "",
brocCode: "",
itemDesc: "",
retail: ""
}
)
};
self.removeItem = function(item) {
self.items.remove(item);
};
};
ko.bindingHandlers.enterPress = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var allBindings = allBindingsAccessor();
element.addEventListener('keydown', function (event) {
var keyCode = (event.which ? event.which : event.keyCode);
if (keyCode === 13 || (!event.shiftKey && keyCode === 9)) {
event.preventDefault();
console.log("hit enter/tab!");
bindingContext.$root.addLine();
return false;
}
return true;
});
}
};
function GetItems() {
//var itemsJSON = @Html.Raw(Json.Encode(Model.brochureItems));
var itemsJSON = '[{"brochureId":1,"itemNo":"1000","brocCode":"1000","itemDesc":"Bicycle","retail":13.5},{"brochureId":1,"itemNo":"1100","brocCode":"1100","itemDesc":"Front Wheel","retail":35},{"brochureId":1,"itemNo":"1120","brocCode":"1120","itemDesc":"Spokes","retail":12.5},{"brochureId":1,"itemNo":"1150","brocCode":"1150","itemDesc":"Front Hub","retail":5},{"brochureId":1,"itemNo":"1151","brocCode":"1151","itemDesc":"Axle Front Wheel","retail":14},{"brochureId":1,"itemNo":"120","brocCode":"120","itemDesc":"Loudspeaker, Black, 120W","retail":12.5},{"brochureId":1,"itemNo":"125","brocCode":"125","itemDesc":"Socket Back","retail":10}]';
var viewModel = new itemsModel(itemsJSON);
ko.applyBindings(viewModel);
}
$(document).ready(function () {
GetItems();
});
Update
Updated code and here is new fiddle.
Upvotes: 0
Views: 73
Reputation: 3634
mapping plugin
does the job for you and creates observable variables for data that is being sent. Once you add a new line then you need to have a sub View Model and add a new instance of that VM
which itself contains observable variables.
var itemsModel = function(items) {
var self = this;
//console.log(JSON.stringify(items));
//self.items = ko.observableArray();
//ko.mapping.fromJS(items,{},self.items)
//self.items = ko.mapping.fromJSON(items);
//self.items = ko.observableArray(items);
self.items = ko.mapping.fromJSON(items);
self.checkItemNo = function(data) {
console.log("blurred!");
//data = ko.observable(data);
//itemModel(data.itemNo)
//var itemNo = $.trim(data.itemNo);
var itemNo = "abc";
if (itemNo != "") {
var item = "";
/*
$.each(fullItemList, function(i, v) {
if (v.No.search(itemNo) != -1) {
item = v.Description;
return;
}
});
if(item != "") {
console.log("found: " + item);
var match = ko.utils.arrayFirst(self.items, function(item) {
return itemNo === item.itemNo;
});
*/
var match = false;
item = "Mudguard front";
if (!match) {
console.log("not a match!");
console.log(data);
data.itemDesc = item;
}
}
}
self.addLine = function() {
self.items.push(new RowViewModel() )
};
self.removeItem = function(item) {
self.items.remove(item);
};
};
var RowViewModel = function(){
var self = this;
self.itemNo = ko.observable();
self.brocCode = ko.observable();
self.itemDesc = ko.observable();
self.retail = ko.observable();
}
Upvotes: 1