Reputation: 15
I don't want to use HTML table/tags as the output will be displayed in a string field. Please see the code snippet below. The expected output should be displayed similar to a table with keys as headers and values of the object are copied respectively to the headers(keys). Please see the below image for expected output. For me the tricky part here is when I have long strings as values, the alignment gets disturbed.
Note: My end goal is to save some characters to avoid overflow on the string field so I came up with below table approach. I have only 4000 characters of length for the string field. If there is a better way to display the object in a string format and is readable for the end users then I don't mind changing the format as long as we are not wasting any characters to make it pretty.
var data = {
"table-info":[
{
"Hostname":"server 756",
"Slot":"NC1",
"VLAN":"test 12",
"Port Type":"Access",
"Port":"port 12",
"Switch":"switch12"
},
{
"Hostname":"",
"Slot":"NI4",
"VLAN":"test 13",
"Port Type":"Tag",
"Port":"port 13",
"Switch":"switch13"
},
{
"Hostname":"",
"Slot":"PC2: 2",
"VLAN":"test 14",
"Port Type":"Access",
"Port":"port 14",
"Switch":"switch14"
},
{
"Hostname":"",
"Slot":"NI4",
"VLAN":"test 15",
"Port Type":"Tag",
"Port":"port 15",
"Switch":"switch15"
},
{
"Hostname":"",
"Slot":"PCI: B",
"VLAN":"test 16",
"Port Type":"Tag",
"Port":"port 16",
"Switch":"switch16"
},
{
"Hostname":"",
"Slot":"PI3: A",
"VLAN":"test 17",
"Port Type":"Tag",
"Port":"port 17",
"Switch":"switch17"
},
{
"Hostname":"server 757",
"Slot":"NC1",
"VLAN":"test 12",
"Port Type":"Access",
"Port":"port 18",
"Switch":"switch18"
},
{
"Hostname":"",
"Slot":"NC4",
"VLAN":"test 13",
"Port Type":"Tag",
"Port":"port 19",
"Switch":"switch19"
},
{
"Hostname":"",
"Slot":"PCI2: 2",
"VLAN":"test 14",
"Port Type":"Access",
"Port":"port 20",
"Switch":"switch20"
},
{
"Hostname":"",
"Slot":"NI4",
"VLAN":"test 15",
"Port Type":"Tag",
"Port":"port 21",
"Switch":"switch21"
}
]
};
var tableInfo = data['table-info'],
headers = Object.keys(tableInfo[0]),
tableInfoLen = tableInfo.length,
headersLen = headers.length;
var result = headers.join('\t');
for (var i = 0; i < tableInfoLen; i++) {
result += '\n';
for (var j = 0; j < headersLen; j++ ) {
if (tableInfo[i][headers[j]] == '') {
result += tableInfo[i][headers[j]] + '\t'+'\t';
}
else {
result += tableInfo[i][headers[j]] + '\t';
}
}
}
console.log(result);
I have achieved the expected output shown in the image by using the below snippet. There is an extra function(padding) which adds spaces based on the length of the given string but again characters are getting eaten up which is not I want.
var data = {
"table-info":[
{
"Hostname":"server 756",
"Slot":"NC1",
"VLAN":"test 12",
"Port Type":"Access",
"Port":"port 12",
"Switch":"switch12"
},
{
"Hostname":"",
"Slot":"NI4",
"VLAN":"test 13",
"Port Type":"Tag",
"Port":"port 13",
"Switch":"switch13"
},
{
"Hostname":"",
"Slot":"PC2: 2",
"VLAN":"test 14",
"Port Type":"Access",
"Port":"port 14",
"Switch":"switch14"
},
{
"Hostname":"",
"Slot":"NI4",
"VLAN":"test 15",
"Port Type":"Tag",
"Port":"port 15",
"Switch":"switch15"
},
{
"Hostname":"",
"Slot":"PCI: B",
"VLAN":"test 16",
"Port Type":"Tag",
"Port":"port 16",
"Switch":"switch16"
},
{
"Hostname":"",
"Slot":"PI3: A",
"VLAN":"test 17",
"Port Type":"Tag",
"Port":"port 17",
"Switch":"switch17"
},
{
"Hostname":"server 757",
"Slot":"NC1",
"VLAN":"test 12",
"Port Type":"Access",
"Port":"port 18",
"Switch":"switch18"
},
{
"Hostname":"",
"Slot":"NC4",
"VLAN":"test 13",
"Port Type":"Tag",
"Port":"port 19",
"Switch":"switch19"
},
{
"Hostname":"",
"Slot":"PCI2: 2",
"VLAN":"test 14",
"Port Type":"Access",
"Port":"port 20",
"Switch":"switch20"
},
{
"Hostname":"",
"Slot":"NI4",
"VLAN":"test 15",
"Port Type":"Tag",
"Port":"port 21",
"Switch":"switch21"
}
]
};
String.prototype.padding = function(n, c)
{
var val = this.valueOf();
if ( Math.abs(n) <= val.length ) {
return val;
}
var m = Math.max((Math.abs(n) - this.length) || 0, 0);
var pad = Array(m + 1).join(String(c || ' ').charAt(0));
//console.log(pad);
return (n < 0) ? pad + val : val + pad;
};
var tableInfo = data['table-info'],
headers = Object.keys(tableInfo[0]),
tableInfoLen = tableInfo.length
headersLen = headers.length;
var result = '';
for (var k = 0; k < headers.length; k++ ) {
result += headers[k].padding(15)+'\t';
}
for (var i = 0; i < tableInfoLen; i++) {
result += '\n';
for (var j = 0; j < headersLen; j++ ) {
if (tableInfo[i][headers[j]] == '') {
result += tableInfo[i][headers[j]].padding(15)+ '\t';
}
else {
result += tableInfo[i][headers[j]].padding(15) + '\t';
}
}
}
console.log(result);
Upvotes: 0
Views: 1144
Reputation: 48610
You will need to measure all the text that will be display, prior to building the table.
I also added a text justification enumeration so that you can center-justify header columns.
OK, I will stop here, but as you can see, it's pretty extensible.
const main = () => {
let jsonData = getJson()['table-info'].map((item, index) => {
return { Index : (index + 1), ...item }; // Add index to front
});
let table = new TableFormater({
scanAll : true, // We would miss some fields without it
spacer: ' | ', // Divide the columns with a pipe
columns: {
'Port Type' : {
align: TextAlignment.CENTER
},
'ExtraLongKeyAlsoTruncated' : {
limit: 6 // Limit the number of characters
}
}
}).fromJson(jsonData);
console.log(table);
};
/*
* @description An enum representing text alignment
*/
const TextAlignment = Object.freeze({
LEFT : Symbol("LEFT"),
RIGHT : Symbol("RIGHT"),
CENTER : Symbol("CENTER")
});
/*
* @description A class used for converting data into text tables
*/
class TableFormater {
constructor(options) {
this.opts = Object.assign({}, TableFormater.DEFAULT_OPTIONS, options);
}
/* @public */
fromJson(json) {
const fields = this.__gatherFields(json, this.opts.scanAll),
colWidths = this.__measureColumns(json, fields);
return [
fields.map((field, index) => {
return this.__format(field, field, index, colWidths, {
align: TextAlignment.CENTER // Force headers to be centered
});
}).join(this.opts.spacer),
json.map(record => {
return fields.map((field, index) => {
return this.__format(record[field], field, index, colWidths);
}).join(this.opts.spacer);
}).join('\n')
].join('\n');
}
/* @private */
__format(value, field, index, colWidths, overrides) {
return this.__alignContent(value,
Object.assign(this.__generateMetaData(field, index, colWidths),
overrides || {}));
}
/* @private */
__alignContent(content, metadata) {
let isNumeric = !isNaN(content);
content = ('' + (content || '')).trim();
if (content.length === 0) {
return content.padEnd(metadata.maxWidth); // If empty, pad away!
}
if (metadata.limit !== -1 && content.length > metadata.limit) {
content = content.substring(0, metadata.limit - 1) + '…';
}
if (content.length === metadata.maxWidth) {
return content; // Optimization
}
let alignment = metadata.align || TextAlignment.LEFT;
if (isNumeric && metadata.align == null) {
alignment = TextAlignment.RIGHT;
}
switch (alignment) {
case TextAlignment.RIGHT:
return content.padStart(metadata.maxWidth);
case TextAlignment.CENTER:
let diff = metadata.maxWidth - content.length;
let offset = Math.floor(metadata.maxWidth - (diff / 2));
return content.padStart(offset).padEnd(metadata.maxWidth);
case TextAlignment.LEFT:
default:
return content.padEnd(metadata.maxWidth);
}
}
/* @private */
__generateMetaData(field, index, colWidths) {
return Object.assign({
maxWidth: colWidths[index],
limit: -1,
field: field
}, this.opts.columns[field]);
}
/* @private */
__gatherFields(data, scanAllRecords) {
return scanAllRecords
? [...new Set(data.flatMap(record => Object.keys(record)))]
: Object.keys(data[0]);
}
/* @private */
__measureColumns(data, fields) {
let colWidths = fields.map(field => this.__measureValue(field, field));
data.forEach(item => {
fields.forEach((field, index) => {
colWidths[index] = Math.max(colWidths[index],
this.__measureValue(item[field], field));
});
});
return colWidths;
}
/* @private */
__measureValue(value, field) {
if (value == null) return 0;
value = ('' + value).trim();
let column = this.opts.columns[field] || {};
return column.limit !== -1 && value.length > column.limit
? column.limit : value.length;
}
}
/* @static */
TableFormater.DEFAULT_OPTIONS = {
columnSpacer : ' ',
columns : {},
scanAll : false
};
const getJson = () => {
return {
"table-info": [{
"Hostname": "server 756",
"Slot": "NC1",
"VLAN": "test 12",
"Port Type": "Access",
"Port": "port 12",
"Switch": "switch12"
}, {
"Slot": "NI4",
"VLAN": "test 13",
"Port Type": "Tag",
"Port": "port 13",
"Switch": "switch13"
}, {
"Slot": "PC2: 2",
"VLAN": "test 14",
"Port Type": "Access",
"Port": "port 14",
"Switch": "switch14",
// This is to demonstrate 'scanAll' and 'limit'
"ExtraLongKeyAlsoTruncated": "I will be truncated!"
}, {
"Slot": "NI4",
"VLAN": "test 15",
"Port Type": "Tag",
"Port": "port 15",
"Switch": "switch15"
}, {
"Slot": "PCI: B",
"VLAN": "test 16",
"Port Type": "Tag",
"Port": "port 16",
"Switch": "switch16"
}, {
"Slot": "PI3: A",
"VLAN": "test 17",
"Port Type": "Tag",
"Port": "port 17",
"Switch": "switch17"
}, {
"Hostname": "server 757",
"Slot": "NC1",
"VLAN": "test 12",
"Port Type": "Access",
"Port": "port 18",
"Switch": "switch18"
}, {
"Slot": "NC4",
"VLAN": "test 13",
"Port Type": "Tag",
"Port": "port 19",
"Switch": "switch19"
}, {
"Slot": "PCI2: 2",
"VLAN": "test 14",
"Port Type": "Access",
"Port": "port 20",
"Switch": "switch20"
}, {
"Slot": "NI4",
"VLAN": "test 15",
"Port Type": "Tag",
"Port": "port 21",
"Switch": "switch21"
}]
}
};
main();
.as-console-wrapper {
top: 0;
max-height: 100% !important;
}
.as-console-wrapper .as-console-row-code,
.as-console-wrapper .as-console-row:after {
font-size: 0.8em;
}
Note: If you didn't already know, if you just want to display this in the console, console.table
is really neat.
Just call: console.table(data['table-info'])
Upvotes: 3