Efficient way to Populate Table with multidimensional JSON - javascript

I'm looking to the most efficient way to convert this json.
{
"Kitchen":{
"protocol":[
"pollin"
],
"id":[
{
"systemcode":31,
"unitcode":1
}
],
"state":"off"
},
"BBQ":{
"protocol":[
"pollin"
],
"id":[
{
"systemcode":31,
"unitcode":4
}
],
"state":"off"
},
"Server":{
"protocol":[
"pollin"
],
"id":[
{
"systemcode":15,
"unitcode":1
}
],
"state":"off"
}
}
Into the following table:
[Name] [Protocol] [Systemcode] [S] [State]
Kitchen pollin 31 1 off
BBQ pollin 31 4 off
Server pollin 15 1 off
My page consists of a table in html and jquery is already loaded.
<table class="table" id="tableDevices">
<thead>
<tr>
<th>Name</th>
<th>Protocol</th>
<th>Systemcode</th>
<th>Unitcode</th>
<th>State</th>
</tr>
</thead>
</table>
Now I'm looking for the fastest way to loop trough the JSON, and populate the table with the data. I first flattened the JSON, and then fed it to the table with a loop. However the code I modified to flatten the JSON was painfully slow. This is the my first time working with version control and javascript and I apparently didn't commit my code properly. I'm looking for the most efficient way to fill this table and I'm stuck myself so can someone show my the most efficient way to do this?

I'm going to assume you've parsed the JSON and have a variable, data, that refers to the parsed result.
Then it's a simple for-in loop:
var tbody = $("<tbody>");
var key, entry, tr;
for (key in data) {
entry = data[key];
tr = $("<tr>");
$("<td>").text(key).appendTo(tr);
$("<td>").text(entry.protocol[0]).appendTo(tr);
$("<td>").text(entry.id[0].systemcode).appendTo(tr);
$("<td>").text(entry.id[0].unitcode).appendTo(tr);
$("<td>").text(entry.state).appendTo(tr);
tr.appendTo(tbody);
}
tbody.appendTo("#tableDevices");
That said, you might look at a templating library like Handlebars instead.
Live Example:
// This stands in for the parsed JSON
var data = {
"Kitchen": {
"protocol": [
"pollin"
],
"id": [{
"systemcode": 31,
"unitcode": 1
}],
"state": "off"
},
"BBQ": {
"protocol": [
"pollin"
],
"id": [{
"systemcode": 31,
"unitcode": 4
}],
"state": "off"
},
"Server": {
"protocol": [
"pollin"
],
"id": [{
"systemcode": 15,
"unitcode": 1
}],
"state": "off"
}
};
var tbody = $("<tbody>");
var key, entry, tr;
for (key in data) {
entry = data[key];
tr = $("<tr>");
$("<td>").text(key).appendTo(tr);
$("<td>").text(entry.protocol[0]).appendTo(tr);
$("<td>").text(entry.id[0].systemcode).appendTo(tr);
$("<td>").text(entry.id[0].unitcode).appendTo(tr);
$("<td>").text(entry.state).appendTo(tr);
tr.appendTo(tbody);
}
tbody.appendTo("#tableDevices");
<table class="table" id="tableDevices">
<thead>
<tr>
<th>Name</th>
<th>Protocol</th>
<th>Systemcode</th>
<th>Unitcode</th>
<th>State</th>
</tr>
</thead>
</table>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
Note that the JSON structure is fairly odd. You have arrays lying around that have just one entry in them.

Why not use Datables instead? You will have most of the burden of populating the table lifted from your shoulders.

Related

Dynamically render information from two arrays

I have this data:
"PensionPlanSummary": [
{
"Type": "DefinedContributionPension",
"Participants": [
{
"Year": 2018,
"Value": 425.0
}
],
"TotalAssets": [
{
"Year": 2018,
"Value": 629282.0
}
],
"NetAssets": [
{
"Year": 2018,
"Value": 629282.0
}
],
},
{
"Type": "Welfare",
"Participants": [
{
"Year": 2018,
"Value": 252.0
},
{
"Year": 2017,
"Value": 389.0
}
],
"TotalAssets": [
{
"Year": 2018,
"Value": 0.0
},
{
"Year": 2017,
"Value": 0.0
}
],
"NetAssets": [
{
"Year": 2018,
"Value": 0.0
},
{
"Year": 2017,
"Value": 0.0
}
]
}
]
I want to render data in this table:
Focus only on Participants. As you can see the data is not populated correct because it is populating by row and for example it should skip 2016 and 2017 for DefinedContributionPension and to fill 2018.
This table is result of this code:
{element.Participants.reverse().map((el, ind) => {
return uniqueYears.map((e, i) => {
// console.log(el.Year);
if (el.Year == e) {
console.log(el.Year);
return (
<td key={ind}>
${numeral(el.Value).format("0,0")}
</td>
);
} else {
return <td key={ind}> - </td>;
}
});
})}
uniqueYears =[2016,2017,2018]
element is the single object (I have another map above). So as you can see I am mapping 1 time the participants and 1 time unique years and finding the 1 record that is true for the condition of equality of the element year. As you can see it is not putting the dash - and it is not populating table correct. I tried looping in other way - first to loop over the uniqueYears array and then to element.Participants but again it not worked as expected. Any ideas how to make it?
P.S.: Table should look like this way:
But lets focus only on participants as in example.
I have tried coming up with a very dirty solution in as little as time as possible, but it seems to be working for me
pensionPlans and uniqueYears are arrays that you have mentioned. My code is below
pensionPlans.map(e => {
if(e.Type === "DefinedContributionPension"){
uniqueYears.map(year => {
e.Participants.map(item => {
if(item.Year === year){
console.log('found')
} else {
console.log("not found")
}
})
})
}
})
Also, I notice that you have used == instead of === while checking for if (el.Year == e). Although correct, this might have some implications later as it doesn't check the type.
You can see my answer running in console here https://jsfiddle.net/z65mp0s8/
Okay, I made a solution for your problem!
So, to get the years from your table, I made a simple function, which just returns the years from your statistics table header, instead of using the pre-defined uniqueYears = [2016, 2017, 2018], but feel free to use it if you need.
Obs.: The advantage of using this function is that you don't need to update your year heading and you uniqueYears array, update only your html table headings and you get the new data for all.
function getYears() {
let years = [];
for(const cell of document.querySelectorAll("[data-statistics='year'] th")) {
if(cell.innerText.trim() !== "") years.push(parseInt(cell.innerText));
}
return years;
}
So if you choose to use the function above make sure you have in yout html table this, the data-statistics="year" is required by the function.
<thead>
<tr data-statistics="year">
<th></th>
<th>2016</th>
<th>2017</th>
<th>2018</th>
</tr>
</thead>
Right after, to get the entries of each one of your data, you can use the Object.entries(), which gives you the key and the entry of each property of your object.
The Array.prototype.splice() is to remove the type property, focusing only on the statistics data.
for(const Plan of PensionPlanSummary) {
let PlanEntries = Object.entries(Plan);
PlanEntries = PlanEntries.splice(1, PlanEntries.length - 1);
for(const pe of PlanEntries) {
// pe[0] -> Title
// pe[1] -> Data
getRow(pe[0], pe[1]);
}
}
Then with your entries with a simple for loops you can achieve your data appending everything into <td>Data</td> and return a html row;
function getRow(index = "", data = null) {
let years = getYears(); // Use your uniqueYears array here.
let html = "<tr>"
// Sort by year
data = data.slice(0);
data.sort((a, b) => {
return a.Year - b.Year;
})
html += `<td>${index}</td>`;
for (const d of data) {
while (years.length !== 0) {
if (d.Year === years[0]) {
html += `<td>${d.Value}</td>`;
years.shift();
break;
} else {
html += `<td>-</td>`;
}
years.shift();
}
}
html += "</tr>";
// console.log(html);
return html;
}
The final result will be this html:
<tr>
<td>Participants</td>
<td>-</td>
<td>-</td>
<td>425</td>
</tr>
Or for your 2 year data participants:
<tr>
<td>Participants</td>
<td>-</td>
<td>389</td>
<td>252</td>
</tr>
Now you only need to append in your html, as you want, take a look at JsFiddle if you need.
It is a little bit dirty code, but hope it helps!

angular ng-repeat through multi level object

I have an object that has different objects within in, here is an example of the object
{
"0":{
"name":{
"type":"unchanged",
"data":"HV010BSML"
},
"colour":{
"type":"unchanged",
"data":"BLACK"
},
"size":{
"type":"unchanged",
"data":"SML"
},
"qty":{
"type":"unchanged",
"data":1
},
"localimg":{
"type":"unchanged",
"data":"/data/New%20Products/HV010%20Black%20with%20pocket%20WM-700x700.jpg"
},
"sml":{
"type":"unchanged",
"data":"2"
},
"stock":{
"type":"unchanged",
"data":"78"
},
"id":{
"type":"unchanged",
"data":"153"
},
"name2":{
"type":"unchanged",
"data":"HV010"
}
},
"1":{
"type":"created",
"data":{
"name":"HV001YSML",
"colour":"YELLOW",
"size":"SML",
"qty":1,
"localimg":"/data/HV001-HV-VEST-YLW-700x700.jpg",
"sml":"1",
"stock":"424",
"id":"8",
"name2":"HV001"
}
}
}
Im wondering if it is possible to iterate over each of these using ng-repeat, because its an object within an object? Ive created a table with ng-repeat and in the "change" col i want to do another ng repeat (which uses the object i showed above) but when i try to show the data in the html its just printing every letter of the array. I'm basically trying to ng-repeat through the object[0] and show it in a readable format such as "HV010BSML is unchanged"
Here is my html :
<div class="row">
<div class="col-lg-12">
<div class="ibox float-e-margins">
<div class="ibox-content">
<table datatable="ng" dt-options="dtOptions" class="table table-striped table-bordered table-hover dataTables-example">
<thead>
<tr>
<th>User</th>
<th>Date</th>
<th>Change</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="(oIndex, o) in orderdata">
<td>{{o.user}}</td>
<td>{{o.date}}</td>
<td> <div ng-repeat="d in formatdata(findhistory(oIndex)) track by $index">{{d}}</div></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
In the controller, findhistory just returns that object i showed and formatdata is this function in the controller
$scope.formatdata=function(obj){
try{
if(obj[0]){
console.log(JSON.stringify(obj[0]));
return JSON.stringify(obj[0]);
}
else{
return;
}
}
catch(ex){}
}
Iterating over object properties
It is possible to get ngRepeat to iterate over the properties of an
object using the following syntax:
<div ng-repeat="(key, value) in myObj"> ... </div>
However, there are a few limitations compared to array iteration:
The JavaScript specification does not define the order of keys returned for an object, so AngularJS relies on the order returned by
the browser when running for key in myObj. Browsers generally follow
the strategy of providing keys in the order in which they were
defined, although there are exceptions when keys are deleted and
reinstated. See the MDN page on delete for more info.
ngRepeat will silently ignore object keys starting with $, because it's a prefix used by AngularJS for public ($) and private ($$)
properties.
The built-in filters orderBy and filter do not work with objects, and will throw an error if used with one.
If you are hitting any of these limitations, the recommended
workaround is to convert your object into an array that is sorted into
the order that you prefer before providing it to ngRepeat. You could
do this with a filter such as toArrayFilter or implement a $watch on
the object yourself.
Extracted from the Angularjs API reference
Personally, i think you could benefit from a little change in your data source, instead of a object holding everything together, use a collection with some similar objects inside. It's easier to understand and has a better support with the ngRepeat.
How about something like this:
[
{
"id": "0",
"data": [
{
"property": "name",
"type":"unchanged",
"data":"HV010BSML"
},
{
"property": "colour",
"type":"unchanged",
"data":"BLACK"
},
{
"property": "size",
"type":"unchanged",
"data":"SML"
},
{
"property": "qty",
"type":"unchanged",
"data":1
},
{
"property": "localimg",
"type":"unchanged",
"data":"/data/New%20Products/HV010%20Black%20with%20pocket%20WM-700x700.jpg"
},
{
"property": "sml",
"type":"unchanged",
"data":"2"
},
{
"property": "stock",
"type":"unchanged",
"data":"78"
},
{
"property": "id",
"type":"unchanged",
"data":"153"
},
{
"property": "name2",
"type":"unchanged",
"data":"HV010"
}
]
},
{
"id": "1"
"data": [
{
"type":"created",
"data":{
"name":"HV001YSML",
"colour":"YELLOW",
"size":"SML",
"qty":1,
"localimg":"/data/HV001-HV-VEST-YLW-700x700.jpg",
"sml":"1",
"stock":"424",
"id":"8",
"name2":"HV001"
}
}
]
}
]

Javascript dataTables populating data issue (no errors returned)

I am using data tables and trying to popular a table with data.
The data I have looks like this:
<table id="mytable" class="display" width="100%"></table>
{
"users": [
{
"id": "6700",
"user": {
"firstName": "somename"
},
"Count": 0
}
]
}
So this is what I've done:
var dataSet = {
"users": [
{
"id": "6700",
"user": {
"firstName": "somename"
},
"Count": 0
}
]
};
jQuery('#mytable').DataTable( {
data: dataSet,
columns: [
{ "users": "id" }
]
});
I'm am not getting any errors but the data is not inserted either.
What I'm I doing wrong here?
In data option you need to provide variable that contains array of objects (dataSet.users). Also in columns.data option you need to define property within each object in the array (id).
Corrected code is shown below.
jQuery('#mytable').DataTable( {
"data": dataSet.users,
"columns": [
{ "data": "id", "title": "ID" }
]
});
See this example for demonstration.

How to populate table values dynamically based on JSON in datatable angular?

I'm using Angular-Datatables. I need to be able to dynamically create the table based on the data that is being returned. In other words, I do not want to specify the column headers.
Example:
json data:
[
{
"id": "2",
"city": "Baltimore",
"state": "MD",
},
{
"id": "5",
"city": "Boston",
"state": "MA",
},
{
"id": "8",
"city": "Malvern",
"state": "PA",
},
]
Column Headers:
id, city, state
Can someone please help with this?
That is actually a good question! With traditional jQuery dataTables it is not a problem, but we have a different kind of declarative setup with angular dataTables, making it more difficult to separate the various tasks. We can delay the population of data with fromFnPromise, but not prevent the dataTable from being instantiated before we want it. I think I have found a solid solution :
First, to avoid instant initialization remove the datatable directive from the markup and give the <table> an id instead, i.e :
<table id="example" dt-options="dtOptions" dt-columns="dtColumns" />
Then load the data resource, build dtColumns and dtOptions and finally inject the datatable attribute and $compile the <table> using the id :
$http({
url: 'data.json'
}).success(function(data) {
var sample = data[0], dtColumns = []
//create columns based on first row in dataset
for (var key in sample) dtColumns.push(
DTColumnBuilder.newColumn(key).withTitle(key)
)
$scope.dtColumns = dtColumns
//create options
$scope.dtOptions = DTOptionsBuilder.newOptions()
.withOption('data', data)
.withOption('dataSrc', '')
//initialize the dataTable
angular.element('#example').attr('datatable', '')
$compile(angular.element('#example'))($scope)
})
This should work with any "array of objects" resource
Demo -> http://plnkr.co/edit/TzBaaZ2Msd9WchfLDLkN?p=preview
NB: Have cleaned up the example JSON, I guess it was a sample and not meant to be working with trailing commas.
Being faced with the same problem, I actually found an easier to implement and much simpler (and safer because of not using $compile) solution.
The only change needed to be made to the html is the addition of an ng-if:
<table ng-if="columnsReady" datatable="" dt-options="dtOptions" dt-columns="dtColumns"/>
What happens is that angular will delay the creation of this node till columnsReady has any value. So now in your code you can get the data you need, and when you have it, you can just set columnsReady to true and angular will do the rest.
$http({
url: 'data.json'
}).success(function(data) {
var sample = data[0], dtColumns = []
//create columns based on first row in dataset
for (var key in sample) dtColumns.push(
DTColumnBuilder.newColumn(key).withTitle(key)
)
$scope.dtColumns = dtColumns
//create options
$scope.dtOptions = DTOptionsBuilder.newOptions()
.withOption('data', data)
.withOption('dataSrc', '')
//initialize the dataTable
$scope.columnsReady = true;
});
Below code which will give you table dynamically based on data
HTML
<div ng-controller="WithAjaxCtrl as showCase">
<table datatable="" dt-options="showCase.dtOptions" dt-columns="showCase.dtColumns" class="row-border hover"></table>
JS
angular.module('showcase.withAjax',['datatables']).controller('WithAjaxCtrl', WithAjaxCtrl);
function WithAjaxCtrl(DTOptionsBuilder, DTColumnBuilder) {
var vm = this;
vm.dtOptions = DTOptionsBuilder.fromSource('data.json')
.withPaginationType('full_numbers');
vm.dtColumns = [
DTColumnBuilder.newColumn('id').withTitle('ID'),
DTColumnBuilder.newColumn('city').withTitle('City'),
DTColumnBuilder.newColumn('state').withTitle('State')
];
}
data.json
[{
"id": 860,
"city": "Superman",
"state": "Yoda"
}, {
"id": 870,
"city": "Foo",
"state": "Whateveryournameis"
}, {
"id": 590,
"city": "Toto",
"state": "Titi"
}, {
"id": 803,
"city": "Luke",
"state": "Kyle"
},
...
]

Issue loading JSON Object into Datatable Jquery

I am using DataTables with Jquery, I have an JSON object data source I want to fetch via Ajax and display in the table.
JSON data is returned from the /live/log url and is formatted as follows :
{
"Logs": [
{
"date": "2015-04-22T14:00:39.086Z",
"eventType": "event1",
"str": "Application startup"
},
{
"date": "2015-04-22T14:01:27.839Z",
"eventType": "event2",
"str": "test Logged in"
}
]
}
My Table HTML :
<table id="example" class="display" cellspacing="0" width="100%">
<thead>
<tr>
<th>Date</th>
<th>Event</th>
<th>Description</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Date</th>
<th>Event</th>
<th>Description</th>
</tr>
</tfoot>
</table>
And finally the JS to fetch and populate the datatable :
$(document).ready(function() {
$('#example').dataTable( {
"ajax": "/live/log",
"columns": [
{"data": "date"},
{"data": "eventType"},
{"data": "str"}
]
});
});
I can see via the debugger the JSON data is being fetched correctly.
I seem to get an error in the datatables js relating to the JSON data.
Value aData in fnInitalise is null - Uncaught TypeError: Cannot read property 'length' of undefined. The Datatable gets stuck saying "Loading..."
I'm sure it's probably due to my JSON data formatting. Can anyone point me in the right direction?
You should access the Log object in data, because it is the array that the column will loop through when constructing your table. The plugin works by assuming that the name of your array is called data, i.e.:
{
"data": [
// Array of objects
]
}
But since you are using Log in your case:
{
"Logs": [
// Array of objects
]
}
...you will need to manually specify the dataSrc attribute (because you are using a custom data property):
$(document).ready(function() {
$('#example').dataTable( {
"ajax": {
"url": "/live/log",
"dataSrc": "Logs"
},
"columns": [
{"data": "date"},
{"data": "eventType"},
{"data": "str"}
]
});
});

Categories

Resources