Angular - ng-repeat not updating on update of nested array - javascript

I am rendering data using ng-repeat through GET request, which retrieves an array.
HTML
<div ng-controller="candidateCtrl" >
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>NO</th>
<th>NAMA</th>
<th>NIP</th>
<th>INSTANSI</th>
<th><span ng-show="animateCandidateAdmin" class="ion-load-a"></span></th>
</tr>
</thead>
<tbody ng-repeat="candidate in candidatesAdmin">
<tr class="well whel">
<td>{{$index + 1}}</td>
<td>{{candidate.candidate_name}}</td>
<td>{{candidate.candidate_nip}}</td>
<td>{{candidate.candidate_institusi}}</td>
<td>
<button class="btn btn-xs btn-success" ng-show="candidate.m_assesment_assesment_id == NULL" ng-click="addCandidate3(candidate.candidate_id)">
</td>
</tr>
</tbody>
</table>
</div><!-- OFF-MAINBAR -->
<div ng-repeat="item in percentage_penilaian" >
<div id="candidate_{{item.m_assesment_assesment_id}}" >
<div class="panel-body">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>NO</th>
<th>NAMA</th>
<th>NIP</th>
<th>INSTANSI</th>
<th>BOBOT</th>
<th>SKOR</th>
<th>NILAI</th>
<th><span ng-show="animateCandidateManagerial" class="ion-load-a"></span>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="candidate in candidates[item.m_assesment_assesment_id]" class="well whel">
<td>{{$index + 1}}</td>
<td>{{candidate.candidate_name}}</td>
<td>{{candidate.candidate_nip}}</td>
<td>{{candidate.candidate_institusi}}</td>
<td>{{candidate.percentage}}%</td>
<td ng-show="candidate.skor != NULL">
<button ng-click="$eval(arrAddCandidate[percentage_penilaian[$parent.$index+1].m_assesment_assesment_id])(candidate.candidate_id)"><i class="ion-arrow-right-a"></i> Mengikuti {{percentage_penilaian[$parent.$index+1].assesment_name}}</button>
</td>
</tr>
</tbody>
</table>
</div><!-- OFF-MAINBAR -->
</div>
</div>
</div>
</div>
JS
<script>
var SITEURL = "<?php echo site_url() ?>";
var selectionApp = angular.module("selectionApp", ["isteven-multi-select"]);
selectionApp.controller('candidateCtrl', function ($scope, $http) {
$scope.candidates = [];
$scope.arrAddCandidate = [];
$scope.getPercentagePenilaian = function () {
var url = SITEURL + 'xxx/xxx/' + 14;
$http.get(url).then(function (response) {
$scope.percentage_penilaian = response.data;
for(var i in response.data){
$scope.arrAddCandidate[response.data[i].m_assesment_assesment_id] = "addCandidate"+response.data[i].m_assesment_assesment_id;
}
})
};
$scope.getCandidateAdmin = function () {
var url = SITEURL + 'api/get_candidate_admin/' + 14;
$http.get(url).then(function (response) {
$scope.candidatesAdmin = response.data;
})
};
$scope.get_3 = function () {
var url = SITEURL + 'xxx/xxx/3__14';
$http.get(url).then(function (response) {
$scope.$apply(function () {
$scope.candidates[3] = response.data;
// $scope.candidates.push(response.data);
});
})
};
$scope.addCandidate3 = function (id) {
$scope.animateCandidateAdmin = true;
var postData = $.param({
candidate_id: id,
assesment_id: 3 });
$.ajax({
method: "POST",
url: SITEURL + "xx/xxx/xxxxx",
data: postData,
success: function (response) {
if(response=='sukses'){
$scope.animateCandidateAdmin = false;
$scope.getCandidateAdmin();
$scope.get_3();
}
}
});
};
$scope.get_5 = function () {
var url = SITEURL + 'xx/xxx/5__14';
$http.get(url).then(function (response) {
$scope.$applyAsync(function () {
$scope.candidates[5] = response.data;
// $scope.candidates.push(response.data);
});
})
};
$scope.addCandidate5 = function (id) {
$scope.animateCandidateAdmin = true;
var postData = $.param({
candidate_id: id,
assesment_id: 5 });
$.ajax({
method: "POST",
url: SITEURL + "xx/xxx/xxxxx",
data: postData,
success: function (response) {
if(response=='sukses'){
$scope.animateCandidateAdmin = false;
$scope.getCandidateAdmin();
$scope.get_5();
}
}
});
};
angular.element(document).ready(function () {
$scope.getPercentagePenilaian();
$scope.get_3;
$scope.get_5;
});
});
</script>
Response from $scope.getCandidateAdmin
[{"candidate_id":"24","candidate_name":"contoh","candidate_nip":"12345","candidate_institusi":"Institusi A","selection_selection_id":"14"}]
Response $scope.getPercentagePenilaian
[{"id":"14","m_assesment_assesment_id":"3","percentage":"50"},
{"id":"15","m_assesment_assesment_id":"5","percentage":"10"}]
Response from $scope.get_3
[{"id":"43","selection_selection_id":"14","m_assesment_assesment_id":"3"
,"candidate_id":"24","m_candidate_id" :"1","candidate_name":"contoh","candidate_nip":"12345","candidate_institusi":"Institusi A","competency_skor":null}]
After I adding candidate, I believe that the $scope.candidates array is updated correctly, however the table in my view does not change. I don't know what i'm doing wrong.

Potential Issue
I think 'm_assesment_assesment_id' is stored as a String and not a Integer, which is the root of your problem.
[
{ "id":"14",
"m_assesment_assesment_id":"3", // Note: the numbers are wrapped in quotes
"percentage":"50"
},
{ "id":"15",
"m_assesment_assesment_id":"5", // JSON parsers will interpret these as Strings, NOT numbers
"percentage":"10"}
]
Background to Understanding Problem
Since the ng-repeat is using the item.m_assesment_assesment_id property, Angular resolves these as Strings. When this String is passed into the Array, the Array is interpreted as a JavaScript Object and not an array. For example:
// Both the following evaluate to the same thing
// Using dot accessor
item.m_assesment_assesment_id
// Using square brackets to access data through Object Notation
item["m_assesment_assesment_id"]
This is why JavaScript interprets your Array as an Object, using item.m_assesment_assesment_id as a key and not and index.
<!-- item.m_assesment_assesment_id will evaluate as a String as the JSON data specifies the String format, triggering Object evaluation, not Array evaluation -->
<tr ng-repeat="candidate in candidates[item.m_assesment_assesment_id]" class="well whel">
Here is some good overview of Array Evaluation:
https://www.w3schools.com/js/js_arrays.asp
Associative Arrays Many programming languages support arrays with
named indexes.
Arrays with named indexes are called associative arrays (or hashes).
JavaScript does not support arrays with named indexes.
In JavaScript, arrays always use numbered indexes.
Solutions
There are a couple of solutions. If you have access to manipulating the JSON formatting, I would recommend correcting the problem there:
{
"id":"14",
"m_assesment_assesment_id": 3, // No quote == Integer
"percentage":"50"
}
But this would require you to have access to the server code constructing the response and there may be architectural reasons for using a string.
Alternatively, you could change your ng-repeat:
<tr ng-repeat="candidate in candidates[parseInt(item.m_assesment_assesment_id)]" class="well whel">
However there is a performance loss as this requires more processing during Angular's $digest() event. This will not be significant for small datasets, but it is worth noting.
If you don't have access to changing the JSON formatting on the server and you don't want to put extra load on the $digest event, your control could process the response data from the AJAX calls. This is often the approach recommended by Angular documentation.
angular.module("selectionApp", ["isteven-multi-select"])
.controller('candidateCtrl', function ($scope, $http) {
$scope.candidates = [];
$scope.arrAddCandidate = [];
$scope.getPercentagePenilaian = function () {
var url = SITEURL + 'xxx/xxx/' + 14;
$http.get(url).then(function (response) {
// USE OUR CONVENIENCE METHOD HERE TO CONVERT STRINGS TO INTEGERS
$scope.percentage_penilaian = processPercentagePenilaian(response.data);
for(var i in response.data){
$scope.arrAddCandidate[response.data[i].m_assesment_assesment_id] = "addCandidate"+response.data[i].m_assesment_assesment_id;
}
})
};
// *** MORE of your code exists between these functions
// ALSO YOU HAVE TO CALL THIS IN THE CALLBACK OF THE APPROPRIATE AJAX CALLBACK
var processPercentagePenilaian = function(items) {
// Loop through the Items
angular.forEach(items, function(item, key) {
// For Every Item, convert the m_assesment_assesment_id to an Integer
item.m_assesment_assesment_id = parseInt(item.m_assesment_assesment_id);
});
// End Controller
});

Related

Consuming Java REST service with AngularJS (issue with UPDATE, DELETE)

I have implemented a REST service with Java and all HTTP methods work correctly when I test it with Postman. Now I decided to learn more about AngularJS and added it for consuming the REST service. The GET request works fine and all products are displayed on an html page. But for some reason Delete and Put methods do not work at all. And I'm having trouble to figure out what causes such behaviour.
I have noticed that the problem arises with methods that involve product id. The entity Product.java has an id field named prod_id.
app.js
angular.module("AppProducts", [])
.constant("baseUrl", "http://localhost:8080/webstore/product")
.controller("ProductsCtrl", function ($scope, $http, baseUrl) {
$scope.currentView = "table";
//Works correctly
$scope.showAll = function () {
$http.get(baseUrl).success(function (data) {
$scope.products = data;
});
}
//if product exists, copy it, otherwise new empty
$scope.editOrCreate = function (product) {
$scope.currentProduct = product ? angular.copy(product) : {};
$scope.currentView = "edit";
}
$scope.create = function (product) {
$http.post(baseUrl, product).success(function (product) {
$scope.products.push(product);
$scope.currentView = "table";
});
}
$scope.update = function (product) {
$http({
url: baseUrl + product.prod_id,
method: "PUT",
data: product
}).success(function (modifiedItem) {
for (var i = 0; i < $scope.products.length; i++) {
if ($scope.products[i].prod_id == modifiedItem.prod_id) {
$scope.products[i] = modifiedItem;
break;
}
}
$scope.currentView = "table";
});
}
$scope.delete = function (product) {
// HTTP DELETE
$http({
method: "DELETE",
url: baseUrl + product.prod_id
}).success(function () {
$scope.products.splice($scope.products.indexOf(product), 1);
});
}
// Save changes
$scope.saveEdit = function (product) {
if (angular.isDefined(product.prod_id)) {
$scope.update(product);
} else {
$scope.create(product);
}
}
$scope.cancelEdit = function () {
$scope.currentProduct = {};
$scope.currentView = "table";
}
$scope.sortType = 'brand'; // set the default sort type
$scope.sortReverse = false; // set the default sort order
$scope.searchProduct = ''; // set the default search/filter term
$scope.showAll();
});
'table' view
<table id="myTable" class="table table-hover">
<thead>
<tr>
<th>Brand</th>
<th>Product Name</th>
<th>Description</th>
<th>Price</th>
<th width="100"></th>
<th width="100"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="product in products | orderBy:sortType:sortReverse">
<td>{{product.brand}}</td>
<td>{{product.productName}}</td>
<td>{{product.description}}</td>
<td>{{product.price}}</td>
<td><button class="btn btn-success" ng-click="editOrCreate(product)">Edit</button></td>
<td><button class="btn btn-danger" ng-click="delete(product)">Delete</button></td>
</tr>
</tbody>
</table>
RestController 'delete' method
#RequestMapping(value = "/product/{id}", method = RequestMethod.DELETE)
public ResponseEntity<?> deleteProduct(#PathVariable("id") int id) {
Product product = productService.getProductById(id);
if (product == null) {
return new ResponseEntity(new CustomError("Unable to delete. Product with id " + id + " not found."),
HttpStatus.NOT_FOUND);
}
productService.deleteProduct(id);
return new ResponseEntity<Product>(HttpStatus.NO_CONTENT);
}
This may be the problem. When you are appending the url like this
baseUrl + product.prod_id // let product.prod_id = 1
you would get resulting string as http://localhost:8080/webstore/product1 which is not defined in your backend. Try changing the assignment to something like this:
baseUrl + "/" + product.prod_id
Or you cold just add an / at end of baseurl. Like this:
.constant("baseUrl", "http://localhost:8080/webstore/product/")

Angular is adding an empty array to a 2D array - where/why?

I'm having a bit of a weird problem and there are now 2 people in my office completely stumped by this (so I hope it's not something embarrassingly obvious!).
As an overview, we have a SQLite database that could contain any number of tables, each with any number of rows or columns. We want to display this data in a page in an Electron app.
We have an Angular page in our Electron app (with Express/Node as a backend) which makes some calls to an Express endpoint (which gets data from the SQLite db) and we end up with two 2D arrays of data - one for the table headers ($scope.tableHeaders) and one for the table content ($scope.documentsList).
The code for the calls is below:
$http.get('http://localhost:3000/tableCount').then(function (res) {
if (res.data.numberoftables) {
$scope.count = res.data.numberoftables;
var i;
$scope.documentsList = new Array();
$scope.tableHeaders = new Array();
for (i = 1; i <= $scope.count; i++) {
var tableNo = i;
//loop through tables
***$http.get('http://localhost:3000/doclist/' + tableNo).then(function (resDocs) {
//get table content
if (resDocs.data) {
***$scope.documentsList.push(resDocs.data);
}
});
$http.get('http://localhost:3000/tableColumns/' + tableNo).then(function (resHeads) {
//get table headers
if (resHeads.data) {
$scope.tableHeaders.push(resHeads.data);
}
});
}
}
});
Just for fun, here's our HTML:
<div id="documentTables">
<div ng-repeat="tableID in getNumberArray(count)">
<table st-table="documentsList[tableID]" class="table table-condensed table-hover">
<thead>
<tr st-safe-src="tableHeaders[tableID]">
<th ng-repeat="col in tableHeaders[tableID]">
{{col}}
</th>
</tr>
<tr st-safe-src="tableHeaders[tableID]" colspan="{{tableHeaders[tableID].length}}">
<th colspan="1" ng-repeat="col in tableHeaders[tableID]">
<input id="{{col + 'searchbox' + tableID}}" st-search="col" placeholder="{{col}}" class="input-sm form-control" type="search" />
</th>
</tr>
</thead>
<tbody>
<tr st-safe-src="documentsList[tableID]" ng-repeat="document in documentsList[tableID]">
<td ng-repeat="col in tableHeaders[tableID]">
{{document[col].value}}
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="{{tableHeaders[tableID].length}}" class="text-center">
<div st-pagination="" st-items-by-page="10" st-displayed-pages="7"></div>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
The problem we're coming across is that no actual data is displayed in the tables. Somewhere between the starred lines (***) in the JS above, an empty array for each table is added. So if we have two tables in our database, we end up with documentsList = Array[4] with documentsList[0] and documentsList[1] being empty arrays. Why/where would this be happening, and how can I fix it?
Note - if you'd like to test this out without our endpoints, try out these variables:
$scope.count = 2;
$scope.tableHeaders = [["TabNo", "Document", "PageNumber", "Date"],["TabNo", "Document", "PageNumber"]];
$scope.documentsList = [];
$scope.documentsList.push(JSON.parse('[{"idANX":1,"pathANX":"Product Overview.pdf","isHeaderANX":1,"isExcludedANX":0,"isNotHyperlinkedANX":0,"isSubHeaderANX":0,"HyperlinkedColumnANX":1,"TabNo":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"","isLastCell":false,"value":"1"},"Document":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"Product Overview.pdf","isLastCell":false,"value":"Product Overview"},"PageNumber":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"","isLastCell":false,"value":"1 - 2"},"Date":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"","isLastCell":true,"value":"18 August 2015"}},{"idANX":2,"pathANX":"Spec.pdf","isHeaderANX":0,"isExcludedANX":0,"isNotHyperlinkedANX":0,"isSubHeaderANX":0,"HyperlinkedColumnANX":1,"TabNo":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"","isLastCell":false,"value":"2"},"Document":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"Spec.pdf","isLastCell":false,"value":"Spec"},"PageNumber":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"","isLastCell":false,"value":"3 - 4"},"Date":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"","isLastCell":true,"value":"1 April 2015"}}]');
$scope.documentsList.push(JSON.parse('[{"idANX":1,"pathANX":"Product Overview.pdf","isHeaderANX":0,"isExcludedANX":0,"isNotHyperlinkedANX":0,"isSubHeaderANX":0,"HyperlinkedColumnANX":1,"TabNo":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"","isLastCell":false,"value":"1"},"Document":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"Product Overview.pdf","isLastCell":false,"value":"Product Overview"},"PageNumber":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"","isLastCell":false,"value":"1 - 2"}},{"idANX":2,"pathANX":"Spec.pdf","isHeaderANX":0,"isExcludedANX":0,"isNotHyperlinkedANX":0,"isSubHeaderANX":0,"HyperlinkedColumnANX":1,"TabNo":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"","isLastCell":false,"value":"2"},"Document":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"Spec.pdf","isLastCell":false,"value":"Spec"},"PageNumber":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"","isLastCell":false,"value":"3 - 4"}}]');
$scope.getNumberArray = function (num) {
var n = new Array(num);
var i;
for (i = 0; i < num; i++) {
n[i] = i;
}
return n;
}
$scope.rowClass = function (row) {
if (row.isSubHeaderANX == 1) {
return 'subheader';
} else if (row.isHeaderANX == 1) {
return 'header';
}
else { return 'doctablerow'; }
}
Also pagination and filtering don't work but that's an issue for when our table actually contains data.
Edit: Probably should have mentioned that this is using angular-smart-table
You should take a look at how asynchronous requests and promises works. To make your code run you could do this: var promises = []; // Creates an array to store the promises
$http.get('http://localhost:3000/tableCount').then(function (res) {
if (res.data.numberoftables) {
$scope.count = res.data.numberoftables;
var i;
$scope.documentsList = new Array();
$scope.tableHeaders = new Array();
var promises = [];
for (i = 1; i <= $scope.count; i++) {
var tableNo = i;
$scope.getHttp(i).then(function(resp){
$scope.documentsList.push(resolutions[0]);
$scope.tableHeaders.push(resolutions[1]);
});
}
}
});
$scope.getHttp = function(tableNo){
var promise;
promise = $http({
url:'http://localhost:3000/doclist/' + tableNo,
method:'get'
});
promises.push(promise);
promise = $http({
url:'http://localhost:3000/tableColumns/' + tableNo,
method:'get'
});
promises.push(promise);
return $q.all(promises);
}
Make the changes accordingly. I did not test it because dont have complete controller.But for sure, if you are looping http, make sure promises are stored.

How to update data from database with api call in angular

I have a api call who give me the list of data, and I am iterating data via ng-repeat (its a list of more than 100 items)
For getting list of data I have call an Api in App Controller in angularjs like this:
var path = serverUrl + 'api/getAllMails';
$http.get(path).then(function (result) {
$scope.mails=result
})
For Iterating the mails in Html file i have use table like the below
<table>
<tr class="header">
<th class="center">Id</th>
<th class="center">Mode of Payment</th>
<th class="center">Payment Collected</th>
<th class="center">Status</th>
</tr>
<tr ng-repeat="mail in mails">
<td>{{mail.id}}</td>
<td>{{mail.paymentType}}</td>
<td>Rs. {{mail.cost}}
<input type="text" ng-model="mail.cost">
<button ng-click="updateCost=(mail.id, mail.cost)">Update Cost</button>
</td>
<td>{{mail.status}}
<input type="text" ng-model="mail.status">
<button ng-click="updateStatus(mail.id, mail.status)">Update Status</button>
</td>
</tr>
</table>
Suppose in the first iterations the cost will be "100" and the status will be "pending". And I have to update this row only, change cost to "1000" and status will be "Delivered".
In my App controller of Angularjs I have create methods. These two methods are calling apis and updating data in database and return the list of updated mails.
$scope.updateStatus = function(mailId, mailStatus) {
var path = serverUrl + 'api/updateStatus';
$http.get(path, {
params: {
mailId: mailId,
mailStatus: mailStatus
}
}).then(function(result) {
$scope.mails = result
})
}
$scope.updateCost = function(mailId, mailCost) {
var path = serverUrl + 'api/updateStatus';
$http.get(path, {
params: {
mailId: mailId,
mailCost: mailCost
}
}).then(function(result) {
$scope.mails = result
})
}
These code are working fine but while it took lot of time to load a page. So what can I do to reduce the loading time or is there any better way to do the same thing.
Any help will be appreciable. Thank you
You are replacing the entire dataset when there is no reason for that, you should only update the row you change. Ensure your updateStatus return the object you update and update that item in $scope.mails
In example
$scope.updateCost = function(mailId, mailCost) {
var path = serverUrl + 'api/updateStatus';
$http.get(path, {
params: {
mailId: mailId,
mailStatus: mailCost
}
}).then(function(result) {
// result is the item you changed
for (var i = $scope.mails.length - 1; i >= 0; i--) {
if($scope.mails[i].id === mailId) {
$scope.mails[i] = result;
return;
}
};
})
}

KnockoutJS observableArray: group data in foreach

The table with the knockout.js bindings currenlty looks lke this:
source total division
00234 4506 div1
30222 456 div2
63321 23 div2
40941 189 div1
The desired output would be something like below. The data needs to grouped by division.
source total
div1
00234 4506
40941 189
div2
30222 456
63321 23
Here's my ViewModel:
var ReportingViewModel;
ReportingViewModel = { Results: ko.observableArray(null) }
The ReportingViewModel gets populated via an ajax request:
ReportingViewModel.Results(data["Data"]["Report"]);
Q: How can I achieve the desired output?
EDIT:
Here's my View:
<table class="table table-condensed" id="reportData">
<thead>
<tr>
<th>source</th>
<th>total</th>
<th>division</th>
</tr>
</thead>
<tbody data-bind="foreach: Results">
<tr>
<td data-bind="text: source"></td>
<td data-bind="text: total"></td>
<td data-bind="text: division"></td>
</tr>
</tbody>
</table>
<script type="text/javascript">
$(document).ready(function () {
ReportingViewModel.Results(null);
e.preventDefault();
var numbers = null;
if ($('#numbersdd').find("option:selected").length > 0) {
numbers = $('#numbersdd').find("option:selected");}
if (numbers != null) {
$.ajax({
url: '/Reporting/ReportData.aspx',
type: 'POST',
data: numbers,
dataType: 'json',
contentType: "application/json",
success: function (data) {
ReportingViewModel.Results(data["Data"]["Report"]);
},
error: function () {
alert('Error Running Report');
}
});
}
else { alert('No Data!'); }
});
var ReportingViewModel;
ReportingViewModel = {
Results: ko.observableArray(null),
}
ko.applyBindings(ReportingViewModel);
});
</script>
You may declare computed field like this:
GroupedResults: ko.computed(function() {
var result = {};
var original = ReportingViewModel.Results();
for (var i = 0; i < original.length; i++) {
var item = original[i];
result[item.division] = result[item.division] || [];
result[item.division].push(item);
}
return result;
})
This computed field will return object like this:
{
div1: [{source: 234, total: 4506, division: 'div1'}]
div2: [{source: 30222, total: 456, division: 'div2'}]
}
As you can see each property is a division and it contains array of records which are related to this division.
Then just bind your view to this new computed field.
If you want to create your computed as part of your ReportingViewModel declaration, do it like this:
var ReportingViewModel = function(data) {
var self = this;
self.Results = ko.observableArray(data);
self.GroupedResults = ko.computed(...)
}
Then your invocation of the object is similar to how you currently have it...but not.
var reportingViewModel = new ReportingViewModel(data["Data"]["Report"]);
ko.applyBindings(reportingViewModel);
Here is a reasonable fiddle on grouping data in Knockout 2.0 that should work for you.
http://jsfiddle.net/rniemeyer/mXVtN/
Most importantly, you should be transforming your data so you have your divisions as an element to loop through and each division has a computed child that returns the matching data. He happens to use an extension on the observableArray property itself to manage this...
ko.observableArray.fn.distinct = function(prop) {
var target = this;
target.index = {};
target.index[prop] = ko.observable({});
ko.computed(function() {
//rebuild index
var propIndex = {};
ko.utils.arrayForEach(target(), function(item) {
var key = ko.utils.unwrapObservable(item[prop]);
if (key) {
propIndex[key] = propIndex[key] || [];
propIndex[key].push(item);
}
});
target.index[prop](propIndex);
});
return target;
};
Then in your markup just data-bind to loop through your divisions.

Knockout observableArray is not binding correctly

I am in the process of learning about knockout/json/mvc et al and have tried to put together an example project, but for some reason I am unable to get the data to bind correctly.
In the code snippet below, I get some JSON data from a web server and then try to map it to a function and then eventually to my knockout observableArray. What I then do is use this observableArray to bind to a HTML table. However the HTML table is not displaying any data. I put a label on to the HTML page and this does print out but with a toString() value of :
[Object object]
five times, which matches the amount of properties in the JSON data.
Can anyone see anything obvious I am missing?
JSON received from web server:
{ "Id": 1, "Name": "Inst123", "Price": 10, "DateTime": "2014-01-16T17:22:43.6383507+00:00", "Description": "Descriptions" };
.
ViewModel
$(document).ready(function () {
var gtViewModel = new GvTradeViewModel();
ko.applyBindings(gtViewModel);
console.log("ViewModel created");
});
var GvTradeViewModel = function () {
var self = this;
self.gvTrades = ko.observableArray([]);
var apiUrl = "http://localhost:57858/api/Trade/1";
console.log("Starting JSON data retrieval from: " + apiUrl);
$.getJSON(apiUrl)
// Handle success event.
.done(function (jsonData) {
if (jsonData.isEmptyObject)
console.log("NoData recieved");
else {
console.log("JSON data: " + jsonData);
var mappedTrades = $.map(jsonData, function (gvTradeData) {
return new GvTrade(gvTradeData);
});
self.gvTrades(mappedTrades);
console.log(self.gvTrades);
}
})
// Handle error/fail event.
.fail(function (jqxhr, textStatus, error) {
var err = textStatus + ", " + error;
console.log("Request Failed: " + err);
});
};
function GvTrade(data) {
this.TradeId = ko.observable(data.TradeId);
this.InstrumentName = ko.observable(data.InstrumentName);
this.DateTime = ko.observable(data.DateTime);
this.Price = ko.observable(data.Price);
this.Description = ko.observable(data.Description);
}
HTML
<table>
<thead>
<tr>
<th>TradeId</th>
<th>InstrumentName</th>
<th>Price</th>
<th>DateTime</th>
<th>Description</th>
</tr>
</thead>
<tbody data-bind="foreach: $data.gvTrades">
<tr>
<td data-bind="text: InstrumentName"></td>
<td data-bind="text: Price"></td>
<td data-bind="text: DateTime"></td>
<td data-bind="text: Description"></td>
</tr>
</tbody>
The JSON coming from your server represents a single object and not an array.
So when you are calling $.map then it does not correctly maps your data as an array, so you will end up some unusable objects.
To fix this you need to make sure that your jsonData containing an array before the map operation, you can do this with calling jQuery.makeArray on it (or you can have an if which is based on your data type decide whether you need to map or not):
var mappedTrades = $.map($.makeArray(jsonData), function (gvTradeData) {
return new GvTrade(gvTradeData);
});
Demo JSFiddle.

Categories