Data not updating correctly with knockout.js - javascript

I just started with knockout.js a week ago, so hopefully this is something easy. I've spent about 5 hours searching Google and this site and none of the suggestions I've seen seem to work. I've tried changing pagedPlayerList to just playerList to remove that part of the code as an issue.
I have this code showing some data with knockout.js
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Attack</th>
<th>Defense</th>
<th>Level</th>
<th>IPH</th>
<th>Syndicate</th>
<th>Last Modified</th>
<th style="width: 100px; text-align:right;" />
</tr>
</thead>
<tbody data-bind=" template:{name:playerTemplateToUse, foreach: pagedPlayerList }"></tbody>
</table>
<ul class="pagination">
<li data-bind="css: { disabled: pageIndex() === 0 }">«</li>
</ul>
<ul class="pagination" data-bind="foreach: allPlayerPages">
<li data-bind="css: { active: $data.pageNumber === ($root.pageIndex() + 1) }"></li>
</ul>
<ul class="pagination">
<li data-bind="css: { disabled: pageIndex() === maxPlayerPageIndex() }">»</li>
</ul>
<script id="itemsPlayerTmpl" type="text/html">
<tr>
<td data-bind="text: id"></td>
<td data-bind="text: name"></td>
<td data-bind="text: att"></td>
<td data-bind="text: def"></td>
<td data-bind="text: lvl"></td>
<td data-bind="text: iph"></td>
<td data-bind="text: synd_name"></td>
<td data-bind="text: $root.lastModDate(last_modified)"></td>
<td class="buttons">
<a class="btn btn-sm btn-primary" data-bind="click: $root.edit" href="#" title="edit"><i class="glyphicon glyphicon-edit"></i></a>
<a class="btn btn-sm btn-primary" data-bind="click: $root.removePlayer" href="#" title="remove"><i class="glyphicon glyphicon-remove"></i></a>
</td>
</tr>
</script>
<script id="editPlayerTmpl" type="text/html">
<tr>
<td data-bind="text: id"></td>
<td><input data-bind="value: name"/></td>
<td><input size="8" data-bind="value: att"/></td>
<td><input size="8" data-bind="value: def"/></td>
<td><input size="3" data-bind="value: lvl"/></td>
<td><input size="8" data-bind="value: iph"/></td>
<td>
<select data-bind="options: $root.syndList, optionsText: 'name', optionsValue: 'id', value: synd_id, selectedOptions: 'synd_id', optionsCaption: 'Please select...'"></select>
</td>
<td data-bind="text: $root.lastModDate(last_modified)"></td>
<td class="buttons">
<a class="btn btn-sm btn-success" data-bind="click: $root.savePlayer" href="#" title="save"><i class="glyphicon glyphicon-ok"></i></a>
<a class="btn btn-sm btn-primary" data-bind="click: $root.cancel" href="#" title="cancel"><i class="glyphicon glyphicon-trash"></i></a>
</td>
</tr>
</script>
It works perfectly when the page is loaded. The problem is, I want to have the data updated automatically every 2 minutes, and when I load the data in the display isn't updated.
Here is the relevant section of javascript
self.playerList = ko.observableArray();
<?php if(isset($playerlist)) { ?>
self.playerList(jQuery.parseJSON('<?php echo addslashes($playerlist); ?>'));
<?php } ?>
self.lastModDate = function(data){
var myDate = new Date(data * 1000);
var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
var month = months[myDate.getMonth()];
var date = myDate.getDate();
var year = myDate.getFullYear();
var time = date+', '+month+' '+year;
return time;
}
// ALL PLAYERS TAB
self.addPlayer = function () {
var newItem = new Player();
self.playerList.push(newItem);
self.selectedItem(newItem);
self.moveToPage(self.maxPlayerPageIndex());
};
self.removePlayer = function (item) {
if (item.id) {
if (confirm('Are you sure you wish to delete this item?')) {
$.post('<?php echo base_url('front/deleteplayer'); ?>', item).complete(function (result) {
if(result = '1'){
toastr.success("The player has been removed.", "");
self.playerList.remove(item);
if (self.pageIndex() > self.maxPlayerPageIndex()) {
self.moveToPage(self.maxPlayerPageIndex());
}
} else {
toastr.error("There was a problem removing the player", "");
}
});
}
}
else {
self.list.remove(item);
if (self.pageIndex() > self.maxPlayerPageIndex()) {
self.moveToPage(self.maxPlayerPageIndex());
}
}
};
self.savePlayer = function () {
var item = self.selectedItem();
$.post('<?php echo base_url('front/saveplayer'); ?>', item, function (result) {
console.log(item);
toastr.success("Your changes have been saved.", "");
self.selectedItem(null);
});
};
self.pagedPlayerList = ko.dependentObservable(function () {
var size = self.pageSize();
var start = self.pageIndex() * size;
return self.playerList().slice(start, start + size);
});
self.maxPlayerPageIndex = ko.dependentObservable(function (list) {
return Math.ceil(self.playerList().length / self.pageSize()) - 1;
});
self.nextPlayerPage = function () {
if (self.pageIndex() < self.maxPlayerPageIndex()) {
self.pageIndex(self.pageIndex() + 1);
}
};
self.allPlayerPages = ko.dependentObservable(function () {
var pages = [];
for (i = 0; i <= self.maxPlayerPageIndex() ; i++) {
pages.push({ pageNumber: (i + 1) });
}
return pages;
});
self.playerTemplateToUse = function (item) {
return self.selectedItem() === item ? 'editPlayerTmpl' : 'itemsPlayerTmpl';
};
// END ALL PLAYERS TAB
self.edit = function (item) {
self.selectedItem(item);
self.currentSynd(item.synd_id);
};
self.cancel = function () {
self.selectedItem(null);
};
self.previousPage = function () {
if (self.pageIndex() > 0) {
self.pageIndex(self.pageIndex() - 1);
}
};
self.moveToPage = function (index) {
self.pageIndex(index);
};
Then this is the binding/update code
// SELF UPDATING DATA
update = function() {
siteModel.updatePlayerList();
console.log(siteModel.playerList);
}
var siteModel = new siteModel();
window.setInterval(update,60000);
ko.applyBindings(siteModel);
This is the updatePlayerList function
self.updatePlayerList = function(){
$.ajax({
url:'<?php echo base_url('front/listplayers'); ?>',
success:function(data) {
var obj = jQuery.parseJSON(data);
self.playerList = (obj);
}
});
}
The first time updatePlayerList fires this is an excerpt of what the server is returning:
[{"id":"19","name":"AlDavisJR","att":"818741","def":"895287","lvl":"227","iph":"2804866","synd_id":"9","last_modified":"1384284327","synd_name":"FIGHT CLUB"},{"id":"15","name":"aLEX","att":"95748","def":"112386","lvl":"227","iph":"16033","synd_id":"15","last_modified":"1384240593","synd_name":"iron"}]
But the console.log(self.playerList); shows a blank value. The second and any subsequent times it runs console.log shows the correct data.
The problem is the table always shows the data that's loaded when the page is first loaded. If I modify the database the ajax call gets the new data back but the site isn't uploaded.

There's a lot of code, so there might be more going on, but the first thing I spot, is that you're updating the data wrong in your self.updatePlayerList function.
var obj = jQuery.parseJSON(data);
self.playerList = (obj); // This assigns obj to self.playerList, overwriting the observableArray
Instead do
self.playerList(obj); // This keeps the observable intact, and assigns obj as it's new internal value
Let me know if that solved it for you!
Edit:
In addition, the reason your console.log is blank the first time, is because updatePlayerList is an asynchronous function (because your ajax call executes asynchronously). So the ajax function will be called, and WHILE it's retrieving the data, you already output the value of your observableArray to the console. Instead, you'll want to wait until the success-callback fires, and log to the console there to check if the value is correct.
Another tip that might be helpful: when you log observables to the console, you'll not get to see the actual value of the observable. The helper functions ko.toJS(yourObservableHere) and ko.toJSON(yourObservableHere) are very helpful for retrieving the actual values of your (nested) observables. The first one makes a plain javascript object out of whatever you put in (removing the observable wrappers), the second does the same, but outputs it all to a JSON string. This can be useful for debugging in the UI (e.g. <div data-bind="text: ko.toJSON(yourViewModel)"></div>

Related

ASP.NET Core - Is there any way to put the viewbag on javascript or jquery?

I tried to put viewbag data to javascript in order to fill the table without refreshing the whole page when modifying any data. And it is not working, but on html it's working .
Here's my code..
fillDatatable();
function fillDatatable() {
$('#TableRecords').html(
#if (Context.Session.GetString("cart") != null)
{
foreach (var item in ViewBag.cart)
{
<tr>
<td>
#item.Items.ItemID
</td>
<td>
#item.Items.ItemModelDescription
</td>
<td class="text-right">
<input id="#("UnitPrice" + item.Items.ItemID)" class="form-control text-right b-r-xl" value="#item.Items.ItemUnitPrice" oninput="return change_unitprice('#item.Items.ItemID')" />
</td>
<td class="text-right">
<input id="#("Quantity" + item.Items.ItemID)" class="form-control text-right b-r-xl" value="#item.Quantity" oninput="return change_quantity('#item.Items.ItemID')" />
</td>
<td class="text-right">
<label id="#("Subtotal" + item.Items.ItemID)">
#(item.Quantity * item.Items.ItemUnitPrice)
</label>
</td>
<td>
<a class="btn btn-sm btn-danger btn-rounded" asp-controller="purchaseorderheader" asp-action="Remove" asp-route-id="#item.Items.ItemID"><span class="fa fa-trash"></span></a>
</td>
</tr>
}
}
);
}
Where do am I wrong? Is there any way to make it right? any recommendation for better way is very much accepted. Thank you so much for your help.
You can get the ViewBag data in js like this:
var obj = #Html.Raw(Json.Serialize(ViewBag.cart));
Besides, you can not write the c# code in the js function. If you don't want to refresh the whole page, you can use ajax to request backend to get the updated data, then replace the data in orignal table with the returned data.
Update:
var purchaseOrderList = new List<trnPurchaseOrderLists>
{
new trnPurchaseOrderLists
{
PurchaseOrderID = "1",
Quantity = 10,
Items = new mfItems
{
ItemID = "1",
ItemModelDescription= "A"
}
},
new trnPurchaseOrderLists
{
PurchaseOrderID = "2",
Quantity = 11,
Items = new mfItems
{
ItemID = "2",
ItemModelDescription= "B"
}
},
};
ViewBag.purchaseOrder = purchaseOrderList;
JS
var obj = #Html.Raw(Json.Serialize(ViewBag.purchaseOrder));
console.log(obj);
Result:
could be your js is running before the document has uploaded? try putting your function into document.onload or window.onload (not sure which it's been a while since I've worked with js and web apps)

knockout contentditable binding with array values

I am trying to create a custom binding to edit content with HTML5 on a table following this link example and I can't get it to work with an observableArray().
The table is being show in the view with foreach data-bindig like this:
<tbody data-bind="foreach: customers">
<tr data-bind="attr: {id: $index}">
<td style="text-align: center;">
<span class="label label-primary" data-bind="html: Id"></span>
</td>
<td data-bind="html: Name, attr: {id: 'Nome'}, contentEditable: true"></td>
</tr>
</tbody>
The view model is this:
function ViewModel() {
var self = this;
self.data = '#jsonList';
self.customers = ko.observableArray(JSON.parse(self.data));
self.editable = ko.observable(false);
for (i = 0; i < self.customers().length; i++) {
self.customers()[i]['Details'] = '/Anagrafica/Details/' + self.customers()[i]['Id'];
self.customers()[i]['Delete'] = '/Anagrafica/Delete/' + self.customers()[i]['Id'];
};
ko.bindingHandlers.htmlEdit= {
update: function (element, valueAccessor) {
var value = ko.unwrap(valueAccessor());
}
};
ko.bindingHandlers.contentEditable = {
init: function (element, valueAccessor, allBindingsAccessor) {
var value = ko.unwrap(valueAccessor()),
htmlEdit= allBindingsAccessor().htmlEdit;
$(element).on("input", function () {
if (ko.isWriteableObservable(htmlEdit)) {
htmlEdit(this.innerHTML);
}
});
},
update: function (element, valueAccessor) {
var value = ko.unwrap(valueAccessor());
element.contentEditable = value;
$(element).trigger("input");
}
};
};
var viewModel = new ViewModel();
ko.applyBindings(viewModel);
At the moment I am confused on the code itself, because I don't understand how to set it to point to the elements on the array.
Note: The array is populated, I have no problems in show the content.
Edit: here I add the JSFiddle https://jsfiddle.net/wxn34p45/ for a better read of the code
Looks like you had a spelling mistake Name instead of name:
<td data-bind="html: name, attr: {id: 'Nome'}, contentEditable: true"></td>
Also customers was not an observableArray (in your fiddle at least):
self.customers = ko.observableArray([{"Id":1111,"name":" [Malena]"},{"Id":2222,"name":" [Maria]"},{"Id":3333,"name":" [Merio]"},{"Id":4444,"name":" [Milena]"}]);
I have updated the fiddle (also got it running with knockout, and I have displayed a copy of the ViewModel to help with debugging), the content is now editable.
<h4>View Model</h4>
<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>
Is this now giving the desired effect?
https://jsfiddle.net/asindlemouat/wxn34p45/1/
EDIT
With the further information provided I have managed to make this editable, I have removed the editable from the customers array and used the one in the ViewModel.
As it is looping through the customers array you need to call the editable observable in the ViewModel using $parent.editable.
<tbody data-bind="foreach: customers">
<tr data-bind="attr: {id: $index}">
<td style="text-align: center;">
<span class="label label-primary" data-bind="html: Id"></span>
</td>
<td data-bind="html: name, attr: {id: 'Nome'}, contentEditable: $parent.editable"></td>
<td></td>
</tr>
</tbody>
Updated fiddle: https://jsfiddle.net/asindlemouat/wxn34p45/8/

Knockout.js Mapping and adding large dataset to ko.observableArray

There are many examples of adding large datasets to a ko.observableArray using the underlying array such as this:
ko.observableArray.fn.pushAll = function(valuesToPush) {
var underlyingArray = this();
this.valueWillMutate();
ko.utils.arrayPushAll(underlyingArray, valuesToPush);
this.valueHasMutated();
return this; //optional
};
The problem with doing it this way is I lose my observables. When I use chrome and pause in debugger, I get the values in my array rather than the function c() which is the observable wrap. I have to also observe many of these variables.
I found what does work is the following:
var model = #Html.Raw(Json.Encode(Model));
vm.POs = ko.mapping.fromJS(model.POs);
The problem is that this is slow. How do I use the underlying array to add but then add the observable wrap to each variable without performance issues?
Here is some more code:
var vm = {
POs: ko.observableArray([]),
headersWithAccounting: ko.observableArray([
{header_name: "DATE CREATED", property: "DATE_CREATED", state: ko.observable('')},
{header_name: "DATE ISSUED", property: "DATE_ISSUED", state: ko.observable('')},
{header_name: "USER CREATED", property: "NAME_USER", state: ko.observable('')},
{header_name: "PO NUMBER", property: "NO_PO", state: ko.observable('')},
{header_name: "ORDER STATUS", property: "NAME_STATUS", state: ko.observable('')},
{header_name: "VENDOR", property: "NAME_VENDOR", state: ko.observable('')},
{header_name: "TOTAL COST", property: "COST_TOTAL", state: ko.observable('')},
{header_name: "CTU", property: "ID_CTU", state: ko.observable('')},
{header_name: "ACCOUNTING CODE", property: "ACCOUNTING_CODE_NAME", state: ko.observable('')},
{header_name: "CLOSE ORDER", property: "ACCOUNTING", state: ko.observable('')}
])
};
function PO() {
var self = this;
self.ID_ORDER = ko.observable();
self.DATE_CREATED = ko.observable();
self.DATE_ISSUED = ko.observable();
self.NAME_STATUS = ko.observable();
self.NAME_VENDOR = ko.observable();
self.NAME_USER = ko.observable();
self.COST_TOTAL = ko.observable();
self.ACCOUNTING_CODE_NAME = ko.observable();
self.ACCOUNTING_CODE_ID = ko.observable();
self.NO_PO = ko.observable();
self.SHOWDETAILS = ko.observable(0);
self.ID_TYPE = ko.observable(0);
self.DESCRIPTION = ko.observable('');
self.FILES = ko.observableArray();
self.POParts = ko.observableArray();
self.ACCOUNTING = ko.observable(0);
self.ID_CTU = ko.observable(0);
self.ACCOUNTING.subscribe(function(val) {
if (vm.avoidCloseOrder() == 0) {
$.ajax({
type: "POST",
url: '#Url.Action("AccountingCloseOrder", "Report")',
dataType: 'JSON',
data: {
orderId: self.ID_ORDER()
},
success: function(msg) {
if (msg != 'Good') {
window.location.href = msg;
}
},
error: function (err) {
alert("Error closing order, please try again");
}
});
}
});
self.ACCOUNTING_CODE_ID.subscribe(function(val) {
if (vm.avoidCloseOrder() == 0) {
$.ajax({
type: "POST",
url: '#Url.Action("AccountingCodeChange", "Report")',
dataType: 'JSON',
data: {
orderId: self.ID_ORDER(),
accountingCodeId: self.ACCOUNTING_CODE_ID()
},
success: function(msg) {
},
error: function (err) {
alert("Error closing order, please try again");
}
});
}
});
}
function POPart() {
var self = this;
self.CATEGORY = ko.observable();
self.SUBCATEGORY = ko.observable();
self.DESCRIPTION = ko.observable();
self.PARTNO = ko.observable();
self.QTY_ORDERED = ko.observable();
self.QTY_RECEIVED = ko.observable();
self.COST = ko.observable();
}
function FILE() {
var self = this;
self.LOCATION = ko.observable();
}
Now the issue is in the razor code with the knockout bindings:
<div class="row">
<div class="col-md-12">
<div data-bind="foreach:POs">
<table class="table-responsive">
<thead data-bind="if: $index() == 0 || ($index() > 0 && vm.POs()[$index()-1].SHOWDETAILS() == 1)">
<tr data-bind="foreach:vm.headersWithAccounting">
<th>
<span data-bind="click:$root.sortPOs.bind(property), text:header_name" style="cursor:pointer"></span><i data-bind="css: state"></i>
</th>
</tr>
</thead>
<tbody class="clickabletbody">
<tr>
<td data-bind="click:$parent.showDetailsFor">
<div data-bind="text:DATE_CREATED"></div>
</td>
<td data-bind="click:$parent.showDetailsFor">
<div data-bind="text:DATE_ISSUED"></div>
</td>
<td data-bind="click:$parent.showDetailsFor">
<div data-bind="text:NAME_USER"></div>
</td>
<td data-bind="click:$parent.showDetailsFor">
<div data-bind="text:NO_PO"></div>
</td>
<td data-bind="click:$parent.showDetailsFor">
<div data-bind="text:NAME_STATUS"></div>
</td>
<td data-bind="click:$parent.showDetailsFor">
<div data-bind="text:NAME_VENDOR"></div>
</td>
<td data-bind="click:$parent.showDetailsFor">
<div data-bind="text:COST_TOTAL"></div>
</td>
<td data-bind="click:$parent.showDetailsFor">
<div data-bind="text:ID_CTU"></div>
</td>
<td>
#Html.DropDownList("ddlVendor", new SelectList(Model.ACCOUNTING_CODE_SELECTLIST, "Value", "Text"), "--Select Accounting Code--", new { #class = "form-control", data_bind = "value:ACCOUNTING_CODE_ID" })
</td>
<td>
<input type="checkbox" style="height:30px; width: 30px;" data-bind="checked:ACCOUNTING, enable:(NAME_STATUS() == 'ACCOUNTING' || NAME_STATUS() == 'CLOSED')" /> //PROBLEM RIGHT HERE!!!!
</td>
</tr>
</tbody>
</table>
<table data-bind="if:SHOWDETAILS, fadeVisible:SHOWDETAILS" style="background-color:antiquewhite" class="table-responsive">
<!-- ko if:(ID_TYPE() == 2 || ID_TYPE() == 3) -->
<thead>
<tr>
<th>
DESCRIPTION
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div data-bind="text:DESCRIPTION"></div>
</td>
</tr>
</tbody>
<!-- /ko -->
<!-- ko if:(ID_TYPE() == 1) -->
<thead>
<tr>
<th>
CATEGORY
</th>
<th>
SUBCATEGORY
</th>
<th>
DESCRIPTION
</th>
<th>
PART NO
</th>
<th>
QTY ORDERED
</th>
<th>
QTY RECEIVED
</th>
<th>
COST
</th>
</tr>
</thead>
<tbody data-bind="foreach:POParts">
<tr>
<td>
<div data-bind="text:CATEGORY"></div>
</td>
<td>
<div data-bind="text:SUBCATEGORY"></div>
</td>
<td>
<div data-bind="text:DESCRIPTION"></div>
</td>
<td>
<div data-bind="text:PARTNO"></div>
</td>
<td>
<div data-bind="text:QTY_ORDERED"></div>
</td>
<td>
<div data-bind="text:QTY_RECEIVED"></div>
</td>
<td>
<div data-bind="text:COST"></div>
</td>
</tr>
</tbody>
<!-- /ko -->
</table>
<table data-bind="if:SHOWDETAILS, fadeVisible:SHOWDETAILS" style="background-color:antiquewhite" class="table-responsive">
<thead>
<tr>
<th>
Files
</th>
</tr>
</thead>
<tbody data-bind="foreach:FILES">
<tr>
<td>
<a data-bind="attr: {href: LOCATION, target: '_blank'}" class="btn btn-primary btn-md">Download File</a>
</td>
</tr>
</tbody>
</table>
<div data-bind="if:SHOWDETAILS"><hr /></div>
</div>
<!-- /ko -->
</div>
</div>
The problem is with the checkbox, chrome console says error:
knockout-3.4.0.js:72 Uncaught TypeError: Unable to process binding "enable: function (){return (NAME_STATUS() =='ACCOUNTING'|| NAME_STATUS() =='CLOSED') }"
Message: NAME_STATUS is not a function
This is because in the value it is no longer a function with knockout bindings, it is simply a value, therefore it is not a function and this error is correct. I am losing this because using the underlying array pushes only the javascript values and is not mapping the observable function.
It is taking roughly 10 seconds for 200 entries to map currently using observables, which is pretty ridiculous if you ask me. What will happen when I have 1000+. Even if I only load 50 of them starting out and use ajax to gather the rest behind the scenes, every time I get more data, it will lag the page for a few seconds until it loads them all. Not sure how to go about fixing this.
Edit:
I just had an AHA moment and fixed the losing binding problem. It is taking roughly 4 seconds now for 232 entries. Would still like to get it faster but heres what I changed.
function PO(data) {
var self = this;
self.ID_ORDER = ko.observable(data.ID_ORDER);
self.DATE_CREATED = ko.observable(data.DATE_CREATED);
self.DATE_ISSUED = ko.observable(data.DATE_ISSUED);
self.NAME_STATUS = ko.observable(data.NAME_STATUS);
self.NAME_VENDOR = ko.observable(data.NAME_VENDOR);
self.NAME_USER = ko.observable(data.NAME_USER);
self.COST_TOTAL = ko.observable(data.COST_TOTAL);
self.ACCOUNTING_CODE_NAME = ko.observable(data.ACCOUNTING_CODE_NAME);
self.ACCOUNTING_CODE_ID = ko.observable(data.ACCOUNTING_CODE_ID);
self.NO_PO = ko.observable(data.NO_PO);
self.SHOWDETAILS = ko.observable(0);
self.ID_TYPE = ko.observable(data.ID_TYPE);
self.DESCRIPTION = ko.observable(data.DESCRIPTION);
self.FILES = ko.observableArray();
if (data.FILES != null) {
for (var i = 0; i < data.FILES.length; i++) {
self.FILES.push(new FILE(data.FILES[i]));
}
}
self.POParts = ko.observableArray();
if (data.POParts != null) {
for (var i = 0; i < data.POParts.length; i++) {
self.POParts.push(new POPart(data.POParts[i]));
}
}
self.ACCOUNTING = ko.observable(data.ACCOUNTING);
self.ID_CTU = ko.observable(data.ID_CTU);
self.ACCOUNTING.subscribe(function(val) {
if (vm.avoidCloseOrder() == 0) {
$.ajax({
type: "POST",
url: '#Url.Action("AccountingCloseOrder", "Report")',
dataType: 'JSON',
data: {
orderId: self.ID_ORDER()
},
success: function(msg) {
if (msg != 'Good') {
window.location.href = msg;
}
},
error: function (err) {
alert("Error closing order, please try again");
}
});
}
});
self.ACCOUNTING_CODE_ID.subscribe(function(val) {
if (vm.avoidCloseOrder() == 0) {
$.ajax({
type: "POST",
url: '#Url.Action("AccountingCodeChange", "Report")',
dataType: 'JSON',
data: {
orderId: self.ID_ORDER(),
accountingCodeId: self.ACCOUNTING_CODE_ID()
},
success: function(msg) {
},
error: function (err) {
alert("Error closing order, please try again");
}
});
}
});
}
function POPart(data) {
var self = this;
self.CATEGORY = ko.observable(data.CATEGORY);
self.SUBCATEGORY = ko.observable(data.SUBCATEGORY);
self.DESCRIPTION = ko.observable(data.DESCRIPTION);
self.PARTNO = ko.observable(data.PARTNO);
self.QTY_ORDERED = ko.observable(data.QTY_ORDERED);
self.QTY_RECEIVED = ko.observable(data.QTY_RECEIVED);
self.COST = ko.observable(data.COST);
}
function FILE(data) {
var self = this;
self.LOCATION = ko.observable(data.LOCATION);
}
And the push function:
ko.observableArray.fn.pushAll = function(valuesToPush)
{
var underlyingArray = this();
this.valueWillMutate();
ko.utils.arrayForEach(valuesToPush, function(item) {
underlyingArray.push(new PO(item));
});
this.valueHasMutated();
return this;
}
Any ideas to make this faster than 4 seconds?
I didn't really get what you mean by "I loose my bindings". It's maybe a debugger artefact (which shows you the value of the observable).
I could also be a "this" issue.
I get this snippet which is working (and you can use the click binding while array is populated)
var elementVM = (function () {
function elementVM(message) {
this.myText = ko.observable(message);
}
elementVM.prototype.changeText = function () {
this.myText(this.myText() + " changed");
};
return elementVM;
}());
var myVM = (function() {
var getText = function(count) {
return "My Text " + (count);
};
var myObservableArray = ko.observableArray([new elementVM(getText(0))]);
return function() {
this.myArray = myObservableArray;
myVM.prototype.populate = function() {
myObservableArray.valueWillMutate();
for(var i = 1; i <= 1000; ++i)
{
myObservableArray().push(new elementVM("My Text " + i));
}
myObservableArray.valueHasMutated();
};
};
}());
var vm = new myVM();
ko.applyBindings(vm);
setTimeout(function() {
var start = new Date();
vm.populate();
var stop = new Date();
document.getElementById("pushAll").innerHTML = "pushallTiming: " + (stop - start);
}, 1000);
li {
list-style: none;
border: 1px solid black;
width: auto;
text-align: center;
}
#pushAll {
background-color: red;
width: auto;
text-align: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div id="pushAll"></div>
<ul data-bind="template: { name: 'my-template', foreach: myArray }"></ul>
<script type="text/html" id="my-template">
<li data-bind="text: myText, click: changeText"></li>
</script>
The actual copying to the array takes less than 1 sec. It is the valueHasMutated() function which is taking a few seconds ands thats just part of KO. I'm glad its not taking long to copy the data into the array. I will try to paginate only 50 entries which should help the DOM load faster. Thanks for everybody who responded.

Knockout.js click binding accessing model values

When alerting the property values inside the object I get a really weird alert message.
The alert call is located at where it saids ISSUE IS HERE *.
The alert I am receiving is https://flic.kr/p/m1CYkD
function c(){if0<arguments.length)returnc.equalityComparer&&c.equalityComparer(d,arguments[0])} ......
JS
$(function(){
ko.applyBindings(new ViewModelBooks());
});
function Book(title, checked_out_to, id)
{
this.title = ko.observable(title);
this.checked_out_to = ko.observable(checked_out_to);
this.id = ko.observable(id);
}
function ViewModelBooks(){
var self = this;
self.library = ko.observableArray();
self.checkOutBook = function(obj){
alert(obj.title); <--- ISSUE IS HERE ********************
};
// Query all books
var url = 'http://portal.internal.urs.org/tools_services/training_library/_vti_bin/listdata.svc/Book?$expand=CheckedOutTo';
$.getJSON(url, function(data){
for (var i = 0; i < data.d.results.length; i++){
var book = data.d.results[i];
var checked_out_to = null;
if (book.CheckedOutTo != null){
checked_out_to = book.CheckedOutTo.Name;
}
self.library.push(new Book(book.Title, checked_out_to, book.Id));
}
});
}
HTML
<tbody data-bind="foreach: library">
<tr><td data-bind="text: title"></td>
<td>
<span data-bind="ifnot: checked_out_to">
<button class="btn_checkout" type="button"
data-bind="click: $parent.checkOutBook">Check Out</button>
</span>
<span data-bind="if: checked_out_to">
<span data-bind="text: checked_out_to"> </span>
</span>
</td>
</tr>
</tbody>
Thanks
As obj.title is an observable (a function), you have to invoke it if you need its value.
Your code should be :
alert(obj.title()); // getter
obj.title('new value'); // setter
See doc

KnockoutJS - extending the shopping cart example

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"}];

Categories