Format an array of Objects to a HTML table - html

I am working in angular and trying to format an API response into a simple HTML table in Angular, this process is generally straightforward when the data is single level eg.
{
property-1: value-1
property-2: value-2
.
.
.
property-n: value-n
}
However my array of Objects looks something like this:
[
index: index_1
value: {
property-1: value-1
property-2: value-2
property-3: value-3
},
index: index_1
value: {
property-1: value-1
property-2: value-2
property-3: value-3
},
index : index_2
value : {
property-1: value-1
property-2: value-2
property-3: value-3
property-4: value-4
}
]
What I am hoping to achieve is one table for each index, so index_1 would get one table with column names matching the keys for index_1 (they are always going to be the same keys when the index is the same) and table data being all the data from the different objects in side the main object (if that makes sense)
Gap in Understanding
Where I am having trouble is that the format of the API response is a little weird in that index_1 can show up for as many times as a result for index_1 is spit out, so I don't think I can use a simple ngFor, now there may be a better way for me to format my data from the API response, at the moment this is how I am going about it:
let results: any[][] = await this.searchAllIndexes(searchTerm);
results.forEach(i => i.forEach(j => {
let json = {
index: j._index,
value: j._source //j._source is an object itself
}
result.push(json) //result is a local array of objects inside the function
}))
this.resultObject = result; //this.result is an array of objects
Below is a console log of my output for reference:

Data formatting
const data = [
{
index: "new_index",
value: {
PLN_TYP_CD: "S",
PLN_TYP_DESC: "Disability",
INSRT_BTCH_ID: 4033598,
LST_UPDT_BTCH_ID: -1,
INSRT_TS: "2017-12-06T11:25:47.891258",
},
},
{
index: "custom_index_5",
value: {
PROV_ASGN_ROLE_TYP_CD: "S",
PROV_ASGN_ROLE_TYP_DESC: "PHYSICIAN IS A SPECIALIST",
POL_ORIG_SRC_SYS_CD: "NDB",
PROV_ASGN_ROLE_TYP_NRML_CD: "S",
},
},
{
index: "custom_index_5",
value: {
PROV_ASGN_ROLE_TYP_CD: "S",
PROV_ASGN_ROLE_TYP_DESC: "SPECIALIST",
POL_ORIG_SRC_SYS_CD: "CRR",
PROV_ASGN_ROLE_TYP_NRML_CD: "S",
},
},
{
index: "custom_index_5",
value: {
PROV_ASGN_ROLE_TYP_CD: "S",
PROV_ASGN_ROLE_TYP_DESC: "PHYSICIAN IS A SPECIALIST",
POL_ORIG_SRC_SYS_CD: "OXF",
PROV_ASGN_ROLE_TYP_NRML_CD: "S",
},
},
];
function listNames(data) {
return data
.map((item) => item.index)
.filter((val, i, arr) => arr.indexOf(val) === i);
}
function listColumns(tableName, data) {
const columnObj = data.find((item) => item.index === tableName).value;
return Object.getOwnPropertyNames(columnObj);
}
function fillRow(tableData, obj) {
const row = [];
for (let i = 0; i < tableData.columns.length; i++) {
row.push(obj[tableData.columns[i]]);
}
tableData.rows.push(row);
}
function formatJSON(data) {
const output = [];
const tables = listNames(data);
for (let i = 0; i < tables.length; i++) {
const tableData = {
title: tables[i],
columns: listColumns(tables[i], data),
rows: [],
};
for (let j = 0; j < data.length; j++) {
if (data[j].index === tableData.title) {
fillRow(tableData, data[j].value);
}
}
output.push(tableData);
}
return output;
}
console.log(formatJSON(data));
HTML
<div *ngFor="let table of tables">
<div>{{ table.title }}</div>
<table>
<thead>
<tr>
<th *ngFor="let column of table.columns">{{ column }}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let row of table.rows">
<td *ngFor="let value of row">{{ value }}</td>
</tr>
</tbody>
</table>
</div>

Related

JSON data calculation and re-formate using Angular

I have a JSON file and I am trying to calculate the JSON file key based on the value and reformating it. My JSON file looks like below:
data=[
{
pet:'Cat',
fruit:'Apple',
fish:'Hilsha'
},
{
pet:'Dog',
fish:'Carp'
},
{
pet:'Cat',
fruit:'Orange',
fish:'Lobster'
}
];
I do like to calculate and formate it like below:
data=[
{
label:'Pet',
total:3,
list:[
{
name:'Cat',
value: 2,
},
{
name:'Dog',
value: 1,
}
]
},
{
label:'Fruit',
total:2,
list:[
{
name:'Apple',
value: 1,
},
{
name:'Orange',
value: 1,
}
]
},
{
label:'Fish',
total:3,
list:[
{
name:'Hilsha',
value: 1,
},
{
name:'Carp',
value: 1,
},
{
name:'Lobster',
value: 1,
}
]
},
];
If anybody can help me, it will be very help for me and will save a day.
I have fixed this task myself. If I have any wrong, you can put your comment fill-free :)
``
ngOnInit(): void {
this.dataService.$data.subscribe(data => {
// Create new object and calculation according to category
let petObj: any = {}
let fruitObj: any = {}
let fishObj: any = {}
data.forEach((el: any) => {
if (el.pet != undefined) {
petObj[el.pet] = (petObj[el.pet] || 0) + 1;
}
if (el.fruit != undefined) {
fruitObj[el.fruit] = (fruitObj[el.fruit] || 0) + 1;
}
if (el.fish != undefined) {
fishObj[el.fish] = (fishObj[el.fish] || 0) + 1;
}
});
// Create list according to category
let pet_list: any = [];
let fruit_list: any = [];
let fish_list: any = [];
for (var key in petObj) {
let pet = {
label: key,
value: petObj[key]
}
pet_list.push(pet)
}
for (var key in fruitObj) {
let fruit = {
label: key,
value: fruitObj[key]
}
fruit_list.push(fruit)
}
for (var key in fishObj) {
let fish = {
label: key,
value: fishObj[key]
}
fish_list.push(fish)
}
// Calculate total sum according to category
var totalPet = pet_list.map((res: any) => res.value).reduce((a: any, b: any) => a + b);
var totalFruit = fruit_list.map((res: any) => res.value).reduce((a: any, b: any) => a + b);
var totalFish = fish_list.map((res: any) => res.value).reduce((a: any, b: any) => a + b);
// Rearrange the JSON
this.rearrangeData = [
{
label: 'Pet',
total: totalPet,
list: pet_list
},
{
label: 'Fruit',
total: totalFruit,
list: fruit_list
},
{
label: 'Fish',
total: totalFish,
list: fish_list
}
]
console.log(this.rearrangeData)
// End rearrange the JSON
});
}
``
You can simplify your function. Take a look this one
group(oldData) {
const data = []; //declare an empty array
oldData.forEach((x) => {
//x will be {pet: 'Cat',fruit: 'Apple',fish: 'Hilsha'},
// {pet: 'Dog',fish: 'Carp'}
// ...
Object.keys(x).forEach((key) => {
//key will be 'pet','fruit',...
const item = data.find((d) => d.label == key); //search in the "data array"
if (item) { //if find it
item.total++; //add 1 to the property total of the element find it
// and search in the item.list the 'Cat'
const list = item.list.find((l) => l.name == x[key]);
//if find it add 1 to the property value of the list
if (list)
list.value++;
else
//if not, add to the list
//an object with property "name" and "value" equal 1
item.list.push({ name: x[key], value: 1 });
} else
//if the element is not in the "array data"
//add an object with properties label, total and list
//see that list is an array with an unique element
data.push({
label: key,
total: 1,
list: [{ name: x[key], value: 1 }],
});
});
});
return data;
}
You can use like
this.dataService.$data.subscribe(data => {
this.rearrangeData=this.group(data)
}
NOTE: this function the labels are 'pet','fruit' and 'fish' not 'Pet', 'Fruit' and 'Fish'
Did you try reading the text leading up to this exercise? That'd be my first approach. After that, I'd use reduce. You can do pretty much anything with reduce.

ES6 first order functions: find array index of first object with child array that contains a value

I want to make a cool higher-order function chain for what I could do (perhaps more verbosely) like this:
for (var idx = 0; idx < collecionA.length; idx++) {
for (item in collectionA[idx].children) {
if (item.sku == "someVal") return idx
}
}
Does anyone see a snazzy way to do this with map/find/filter/reduce etc.? I keep wanting to use forEach but then get pwnd when I realize I can't return from it.
Something like:
return collectionA.children.findIndex( (child) => child.children.oneOfThemIncludesAnObjectWithThisProperty("someVal"))
Use Array.findIndex() on the outer collection. For each item, iterate the children with Array.some(), and check if the value of the property (sku) matches the requested value. As soon as a matching value is found, some returns true immediately, and findIndex returns the current index.
const collection = [{"children":[{"sku":"someOtherVal"}]},{"children":[{"sku":"someVal"}]},{"children":[{"sku":"someOtherVal"}]}];
const findIndexWithChildProp = (arr, prop, val) =>
arr.findIndex(({ children }) =>
children.some(({ [prop]: v }) => v === val));
const result = findIndexWithChildProp(collection, 'sku', 'someVal');
console.log(result);
This could be what you are looking for:
function func() {
var index = -1;
collectionA.forEach((p, i) => p.children.forEach(item => {
if (item.sku == "someVal") index = i;
}));
return index;
}
var collectionA = [{
children: [{
sku: "someOtherVal"
}]
}, {
children: [{
sku: "someVal"
}]
}, {
children: [{
sku: "someOtherVal"
}]
}]
console.log(func());

Merging 2 REST endpoints to a single GraphQL response

New to graphQL, I'm Using the following schema:
type Item {
id: String,
valueA: Float,
valueB: Float
}
type Query {
items(ids: [String]!): [Item]
}
My API can return multiple items on a single request of each type (A & B) but not for both, i.e:
REST Request for typeA : api/a/items?id=[1,2]
Response:
[
{"id":1,"value":100},
{"id":2,"value":30}
]
REST Request for typeB : api/b/items?id=[1,2]
Response:
[
{"id":1,"value":50},
{"id":2,"value":20}
]
I would like to merge those 2 api endpoints into a single graphQL Response like so:
[
{
id: "1",
valueA: 100,
valueB: 50
},
{
id: "2",
valueA: 30,
valueB: 20
}
]
Q: How would one write a resolver that will run a single fetch for each type (getting multiple items response) making sure no unnecessary fetch is triggered when the query is lacking the type i.e:
{items(ids:["1","2"]) {
id
valueA
}}
The above example should only fetch api/a/items?id=[1,2] and the graphQL response should be:
[
{
id: "1",
valueA: 100
},
{
id: "2",
valueA: 30
}
]
So I assumed you are using JavaScript as the language. What you need in this case is not to use direct query, rather use fragments
So the query would become
{
items(ids:["1","2"]) {
...data
}}
fragment data on Item {
id
valueA
}
}
Next in the resolver we need to access these fragments to find the fields which are part of the fragment and then resolve the data based on the same. Below is a simple nodejs file with same
const util = require('util');
var { graphql, buildSchema } = require('graphql');
var schema = buildSchema(`
type Item {
id: String,
valueA: Float,
valueB: Float
}
type Query {
items(ids: [String]!): [Item]
}
`);
var root = { items: (source, args, root) => {
var fields = root.fragments.data.selectionSet.selections.map(f => f.name.value);
var ids = source["ids"];
var data = ids.map(id => {return {id: id}});
if (fields.indexOf("valueA") != -1)
{
// Query api/a/items?id=[ids]
//append to data;
console.log("calling API A")
data[0]["valueA"] = 0.12;
data[1]["valueA"] = 0.15;
}
if (fields.indexOf("valueB") != -1)
{
// Query api/b/items?id=[ids]
//append to data;
console.log("calling API B")
data[0]["valueB"] = 0.10;
data[1]["valueB"] = 0.11;
}
return data
},
};
graphql(schema, `{items(ids:["1","2"]) {
...data
}}
fragment data on Item {
id
valueA
}
`, root).then((response) => {
console.log(util.inspect(response, {showHidden: false, depth: null}));
});
If we run it, the output is
calling API A
{ data:
{ items: [ { id: '1', valueA: 0.12 }, { id: '2', valueA: 0.15 } ] } }
If we change the query to
{
items(ids:["1","2"]) {
...data
}}
fragment data on Item {
id
valueA
valueB
}
}
The output is
calling API A
calling API B
{ data:
{ items:
[ { id: '1', valueA: 0.12, valueB: 0.1 },
{ id: '2', valueA: 0.15, valueB: 0.11 } ] } }
So this demonstrates how you can avoid call for api A/B when their fields are not needed. Exactly as you had asked for

How to find depth of nodes in a nested json file?

I found many solutions to find depth of nodes in a nested json file. but it throws me an error "maximum recursion depth exceeded "
when it set maximum recursion limit, it says "process exceeded with some error code"
As a part of my problem, I also need to find out key names of each node in the json file.
example json :
"attachments": {
"data": [
{
"media": {
"image": {
"height": 400,
"src": "https://scontent.xx.fbcdn.net/v/t1.0-1/10250217_10152130974757825_8645405213175562082_n.jpg?oh=904c1785fc974a3208f1d18ac07d59f3&oe=57CED94D",
"width": 400
}
},
"target": {
"id": "74286767824",
"url": "https://www.facebook.com/LAInternationalAirport/"
},
"title": "Los Angeles International Airport (LAX)",
"type": "map",
"url": "https://www.facebook.com/LAInternationalAirport/"
}
]
}
the output should be:
nodes:
[data [media [image[height,width,src]], target[id,url], title, type, url]]
depth: 4
If anyone else came here and found that the accepted answer does not work because Object.keys() for a string returns an array of each character of string and thus for either large objects or objects with large strings it just fails.
Here is something that works ->
function getDepth(obj){
if(!obj || obj.length===0 || typeof(obj)!=="object") return 0;
const keys = Object.keys(obj);
let depth = 0;
keys.forEach(key=>{
let tmpDepth = getDepth(obj[key]);
if(tmpDepth>depth){
depth = tmpDepth;
}
})
return depth+1;
}
Exmaple - https://jsfiddle.net/95g3ebp7/
Try the following function:
const getDepth = (
// eslint-disable-next-line #typescript-eslint/no-explicit-any
obj: Record<string, any>,
tempDepth?: number
): number => {
let depth = tempDepth ? tempDepth : 0;
if (obj !== null) {
depth++;
if (typeof obj === 'object' && !Array.isArray(obj)) {
const keys = Object.keys(obj);
if (keys.length > 0)
depth = Math.max(
...keys.map((key) => {
return getDepth(obj[key], depth);
})
);
} else if (Array.isArray(obj)) {
if (obj.length > 0)
depth = Math.max(
...obj.map((item) => {
return getDepth(item, depth);
})
);
}
}
return depth;
};
If you try this, I believe you have to get 7.
console.log(getDepth({
a: {
b: { a: [{ a: { a: [] } }] }
}
}));
function geth(obj) {
var depth = 0;
var k = Object.keys(obj);
console.log(k);
for (var i in k) {
var tmpDepth = geth(obj[k[i]]);
if (tmpDepth > depth) {
depth = tmpDepth
}
}
return 1 + depth;
}

How to create DataTable filled by JSON when number of columns is unknown

I have a table in my web-application, which is created like this:
<table id="mygrid">
<thead>
<th>Col1</th>
<th>Col2</th>
<th>Col3</th>
</thead>
</table>
<script type="text/javascript">
$(document).ready(function() {
window.oTable = $("#mygrid").dataTable({
"bServerSide": true,
"bSort": false,
"sAjaxSource": "#Url.Action("MyFunction", "Events")",
"fnServerParams": function(aoData) {
aoData.push({ name: "arg1", value: "#Model.arg1" });
},
"aoColumns": [
{ "mDataProp": "Column1" },
{ "mDataProp": "Column2" },
{ "mDataProp": "Column3" }
],
"bJQueryUI": true,
"sPaginationType": "full_numbers",
"bProcessing": false
});
I fill it with function that returns JSON result, like this:
ActionResult MyFunction(string arg1, ...)
{
List<string> someList = ...;
object array = eventsList.Select(str => new
{
Column1 = str + "1",
Column2 = str + "2",
Column3 = str + "3"
});
var result = new
{
sEcho = ...,
iTotalRecords = ...,
iTotalDisplayRecords = ...,
aaData = array
};
return Json(result, JsonRequestBehavior.AllowGet);
}
Now I want to generate table dynamically, so I don't know the number of columns in design time. For example:
<table id="mygrid"><thead>
#{
foreach (string col in colNames)
<th>#col</th>
}
</thead></table>
Could you please advise how should I change my Javascript and C# code to fill the table in similar way? Is it possible? I can generate "mDataProp" lines in Javascript code like I generate columns, but how can I create JSON result in C# code?
Added:
I have solved the problem with controller. As I discovered, the List of Dictionaries is serialized to exactly the same JSON as the list of anonymous objects, so I wrote this:
for (int i = 0; i < n; ++i)
{
list.Add(new Dictionary<string, string>());
foreach (int colName in colNames) events[i][colName] = cellValue;
}
var result = new
{
...,
aaData = list
};
return Json(result, JsonRequestBehavior.AllowGet);
Now I have new question. I cannot generate "aoColumns" array using C# loop like I generated tags in HTML code. I tried like this:
"aoColumns": [
#{
for (int i = 0; i < n; ++i)
{
string colName = "Column" + i.ToString();
{ "mDataProp": "#colName" },
}
}
],
but it does not work. How can I do it?
DataTables does not allow to change column dynamically but you can get columns before data and load datatable on its callback function...
$.ajax('url/columns', function(data){
//write table header
var options =
"bProcessing": true,
"bServerSide": true,
"sAjaxSource": getGridDataUrl,
"iDisplayLength": 10,
"aoColumns": //column settingssss
};
$('#myDataTable').dataTable(options);
});
I solved my problems. Generating JavaScript is easier that I thought. The problem was that when I generated the aoColumns array as the C# string and then assigned it to "aoColumns" property like this:
"aoColumns": [
#propertyStr
],
the compiler somehow hid the quotes and braces.
The right way was to do this:
"aoColumns": [
#Html.Raw(propertyStr)
],