This is a follow-up post of my previous question: Displaying JavaScript object's structure on page with HTML which looked at how to display all properties of a JavaScript object on a page similar to how the console behaves in browsers. In this first question, I had figured out how to render object that had cycles.
Receiving valuable advice from Gerrit0, I changed a couple of things:
used
template
to prepare the HTML elements with JavaScript this allows to safely parse string as strings, not HTML (usingtextContent
instead ofinnerHTML
)used the
details
element to make the foldable containersadded some CSS styling
Here's how it looks like:
HTML templates
<template id="keyValue">
<div class="csle-property">
<span class="csle-key"></span>:
<span class="csle-value"></span>
</div>
</template>
<template id="folder">
<summary>
<div>
<div class="csle-key"></div>:
<div class="csle-preview"></div>
</div>
</summary>
</template>
JavaScript code:
/**
* Shows all the object's properties with a depth of 1
* @params {Object} object, its first level properties are shown
* {HTMLElement} parent the element in which are displayed the properties
* @return {undefined}
*/
const showObject = (object, parent = document.body) => {
const template = {
keyValue: document.querySelector('template#keyValue'),
folder: document.querySelector('template#folder')
}
Object.entries(object).forEach(([key, value]) => {
const objectClass = (value && value.constructor) ? value.constructor : undefined;
const isContainer = (objectClass == Object) || (objectClass == Array);
if (isContainer) { // !better naming for (objects, arrays, maps, sets)?
const element = document.importNode(template.folder.content, true);
const children = {
key: element.querySelector('.csle-key'),
preview: element.querySelector('.csle-preview')
}
children.key.textContent = key;
children.preview.classList.add('csle-' + objectClass.name);
// show four properties maximum per preview
Object.entries(value).some(([k, v], i) => {
type = (v && v.constructor) ? v.constructor : undefined;
const keyValue = document.importNode(template.keyValue.content, true);
const keyValueElement = {
key: keyValue.querySelector('.csle-key'),
value: keyValue.querySelector('.csle-value')
}
// all types need different formatting
switch (type) {
case Object:
keyValueElement.value.textContent = `{...}`;
break;
case Array:
keyValueElement.value.textContent = `Array(${v.length})`;
break
default:
keyValueElement.value.textContent = `${v}`;
}
keyValueElement.key.textContent = k;
keyValueElement.value.setAttribute('class', 'csle-' + typeof v);
children.preview.appendChild(keyValue);
return i == 4;
});
const wrapper = document.createElement('details'); // !couldn't add event listener on <details>
wrapper.appendChild(element)
wrapper.addEventListener('toggle', () => {
showObject(value, wrapper)
}, { once: true });
parent.appendChild(wrapper);
} else {
const element = document.importNode(template.keyValue.content, true);
const children = {
key: element.querySelector('.csle-key'),
value: element.querySelector('.csle-value')
}
children.key.textContent = key;
children.value.textContent = value; // !undefined and null won't be parsed
children.value.setAttribute('class', 'csle-' + typeof value);
parent.appendChild(element);
}
});
};
Full code:
/**
* Shows all the object's properties with a depth of 1
* @params {Object} object, its first level properties are shown
* {HTMLElement} parent the element in which are displayed the properties
* @return {undefined}
*/
const showObject = (object, parent = document.body) => {
const template = {
keyValue: document.querySelector('template#keyValue'),
folder: document.querySelector('template#folder')
}
Object.entries(object).forEach(([key, value]) => {
const objectClass = (value && value.constructor) ? value.constructor : undefined;
const isContainer = (objectClass == Object) || (objectClass == Array);
if (isContainer) { // !better naming for (objects, arrays, maps, sets)?
const element = document.importNode(template.folder.content, true);
const children = {
key: element.querySelector('.csle-key'),
preview: element.querySelector('.csle-preview')
}
children.key.textContent = key;
children.preview.classList.add('csle-' + objectClass.name);
// show four properties maximum per preview
Object.entries(value).some(([k, v], i) => {
type = (v && v.constructor) ? v.constructor : undefined;
const keyValue = document.importNode(template.keyValue.content, true);
const keyValueElement = {
key: keyValue.querySelector('.csle-key'),
value: keyValue.querySelector('.csle-value')
}
switch (type) {
case Object:
keyValueElement.value.textContent = `{...}`;
break;
case Array:
keyValueElement.value.textContent = `Array(${v.length})`;
break
default:
keyValueElement.value.textContent = `${v}`;
}
keyValueElement.key.textContent = k;
keyValueElement.value.setAttribute('class', 'csle-' + typeof v);
children.preview.appendChild(keyValue);
return i == 4;
});
const wrapper = document.createElement('details'); // !couldn't add event listener on <details>
wrapper.appendChild(element)
wrapper.addEventListener('toggle', () => {
showObject(value, wrapper)
}, {
once: true
});
parent.appendChild(wrapper);
} else {
const element = document.importNode(template.keyValue.content, true);
const children = {
key: element.querySelector('.csle-key'),
value: element.querySelector('.csle-value')
}
children.key.textContent = key;
children.value.textContent = value; // !undefined and null won't be parsed
children.value.setAttribute('class', 'csle-' + typeof value);
parent.appendChild(element);
}
});
};
/**
* For demonstration purposes
*/
const obj = {
integer: 12,
string: 'Hello World',
boolean: true,
htmlString: '<b>Nope, not bold</b>',
object: {
innerArray: ['first', 'second'],
innerString: 'yes',
innerInteger: 12,
innerObject: {
first: 1,
second: 2
}
},
array: ['<b>bold text</b>', 'two'],
function: function() {}
};
obj['cycle'] = obj;
showObject(obj, document.querySelector('.container'))
/**
* // all properties for the console get the prefix "csle-"
*/
/**
* Console frame
*/
.container {
background: #242424;
padding: 8px 15px;
}
/**
* Property value's styling
* csle-{boolean, undefined, string, Array, Object}
*/
.csle-boolean,
.csle-undefined {
color: #9283d1;
}
.csle-string {
color: #ca3b38; /*#e93f3b;*/
}
.csle-string:before,
.csle-string:after { content: '"'; }
.csle-Array:before { content: '['; }
.csle-Array:after { content: ']'; }
.csle-Object:before { content: '{'; }
.csle-Object:after { content: '}'; }
/**
* <summary> styling
*/
summary {
margin-left: 18px;
padding-left: 0px;
font-size: 20px;
display: flex;
align-items: center;
margin-bottom: 2px;
}
summary::-webkit-details-marker {
color: #8d8d8d;
text-shadow: 1em 0.1em #242424;
}
/**
* properties styling
*/
details > *:not(:first-child) {
margin-left: 22px;
}
summary > div {
display: inline-block;
width: 100%;
}
.csle-property, summary {
font-family: "Lucida Grande", sans-serif;
font-size: 17px;
padding: 1px;
overflow: hidden;
padding-left: 17px;
color: #acabab;
}
.csle-property {
line-height: 23px;
margin-bottom: 2px;
font-size: 18px;
}
.csle-property > span {
font-size: 17px;
}
.csle-preview > .csle-property {
display: inline;
margin: 0px;
padding: 0px;
margin: -2px;
color: #acabab;
font-size: 18px;
}
.csle-preview > .csle-property > span {
font-size: 17px;
}
.csle-preview > .csle-property:not(:first-child) > .csle-key:before {
content: ', ';
}
.csle-key, summary {
color: #a545ac;
font-size: 17px
}
.csle-key, .csle-preview {
display: inline;
}
<div class="container"></div>
<template id="keyValue">
<div class="csle-property">
<span class="csle-key"></span>:
<span class="csle-value"></span>
</div>
</template>
<template id="folder">
<summary>
<div>
<div class="csle-key"></div>:
<div class="csle-preview"></div>
</div>
</summary>
</template>
Issues:
my function
showObject
seems to big.Unsure if I'm using ECMAScript 6 properly
some of the variable names bother me (
element
,childrenPreview
,isContainer
) as they aren't very clear.I can't attach an event listener on the imported template
<details>
because it is aDocumentFragment
. To fix that I had to (line 62
of the snippet):- only import
<summary>
as atemplate
- create the
<details>
element manually - append
<summary>
inside<details>
- attach the event listener to
<details>
Any workaround?- only import
I'm looking forward to reading your comments.
type
first line in the inner loopsome
You should always add the "use strict" directive as that type of bug is very bad. \$\endgroup\$ – Blindman67 Jul 3 '18 at 12:35{
and before}
, line feeds just take up vertical space. If you do choose to use them, you should probably pad theif
andelse
equally. \$\endgroup\$ – shreyasminocha Jul 10 '18 at 19:08