Displaying JavaScript object's structure on page with HTML
Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
5
down vote
favorite
I wanted to reproduce the type of navigation system found in the console of Firefox and Chrome: you can explore an object's properties by unfolding boxes:
So I'm searching to:
display all of the object's properties (even if they're nested)
being able to fold/unfold the properties if themselves are objects
Element handling
I have written two functions createElement
and appendElement
, I will need them later:
/**
* Creates an HTML element without rendering in the DOM
* @params String htmlString is the HTML string that is created
* @return HTMLElement is the actual object of type HTMLElement
*/
const createElement = htmlString =>
const div = document.createElement('div');
div.innerHTML = htmlString;
return div.firstChild;
/**
* Appends the given element to another element already rendered in the DOM
* @params String or HTMLElement parent is either a CSS String selector or a DOM element
* String or HTMLElement element is either a HTML String or an HTMLElement
* @return HTMLElement the appended child element
*/
const appendElement = (parent, element) =>
element = element instanceof HTMLElement ? element : createElement(element);
return (parent instanceof HTMLElement ? parent : document.querySelector(parent))
.appendChild(element);
Version 1
My initial attempt was to use a recursive approach. Essentially each level would call the function for each of its children which would, in turn, call the function for their own children.
In the end, it would result in having the complete tree displayed on the page.
const showContent = (object, parent) =>
Object.keys(object).forEach(e =>
if(object[e] && object[e].constructor == Object)
showContent(object[e], appendElement(parent, `<div class="level fold"><div class="parent"><span>-</span> $e<div></div>`));
else
appendElement(parent, `<div class="level">$e: <span>$object[e]</span></div>`)
);
// display the object's property
showContent(obj, 'body');
// toggle function to fold/unfold the properties
const toggle = () => event.target.parentElement.classList.toggle('fold');
// listen to click event on each element
document.querySelectorAll('.parent span').forEach(e => e.parentElement.addEventListener('click', toggle));
const showContent = (object, parent) =>
Object.keys(object).forEach(e =>
if(object[e] && object[e].constructor == Object)
showContent(object[e], appendElement(parent, `<div class="level fold"><div class="parent"><span>-</span> $e<div></div>`));
else
appendElement(parent, `<div class="level">$e: <span>$object[e]</span></div>`)
);
// display the object's property
showContent(obj, 'body');
// toggle function to fold/unfold the properties
const toggle = () => event.target.parentElement.classList.toggle('fold');
// listen to click event on each element
document.querySelectorAll('.parent span').forEach(e => e.parentElement.addEventListener('click', toggle));
body>.levelposition:relative;left:25%;width:50%.levelbackground:lightgrey;border:2px solid brown;padding:4px;margin:4px;overflow:hidden.level>spancolor:#0366d5.level>.parentcolor:green;display:inline-block;width:100%.foldheight:18px
<script>const createElement=htmlString=>const div=document.createElement('div');div.innerHTML=htmlString;return div.firstChild;const appendElement=(parent,element)=>element=element instanceof HTMLElement?element:createElement(element);return(parent instanceof HTMLElement?parent:document.querySelector(parent)).appendChild(element);const obj = innerObj:other:'yes',note:12,innerObj:other:'no',note:1,name:'one',int:1225,bool:true;</script>
Here are a few things I didn't like about this code:
there's an event on each property element, also the event listener needs to be set after the element has been added to the DOM. So calling
showContent
before the event handlers doesn't feel natural.this version doesn't support circular structures. For example:
let obj = ;
obj['obj'] = obj;
showContent(obj);will fail...
So this won't work for me. I need something able to handle cycles without too much trouble and that would not require to add an event listener each time a new property is unfolded.
Version 2
I came up with a better version that solved all these problems:
/**
* Shows all the object's properties with a depth of 1
* @params Object object, its first level properties are shown
* String or HTMLElement parent the element in which are displayed the properties
* @return undefined
*/
const showObject = (object, parent='body') =>
Object.entries(object).forEach(([key, value]) =>
if(value && value.constructor == Object)
const element = appendElement(parent, `<div class="level fold"><div class="parent"><span>-</span> $key<div></div>`);
element.addEventListener('click', () => showObject(value, element), once: true);
else
appendElement(parent, `<div class="level">$key: <span>$value</span></div>`);
);
;
/**
* Toggles the CSS class .fold on the provided element
* @params HTMLElement element on which to toggle the class
* @return undefined
*/
const fold = element => element.classList.toggle('fold');
/**
* Document click listener
*/
document.addEventListener('click', function()
const target = event.target;
const isFoldable = target.classList.contains('parent');
if(isFoldable)
fold(target.parentElement);
);
/**
* Shows all the object's properties with a depth of 1
* @params Object object, its first level properties are shown
* String or HTMLElement parent the element in which are displayed the properties
* @return undefined
*/
const showObject = (object, parent='body') =>
Object.entries(object).forEach(([key, value]) =>
if(value && value.constructor == Object)
const element = appendElement(parent, `<div class="level fold"><div class="parent"><span>-</span> $key<div></div>`);
element.addEventListener('click', () => showObject(value, element), once: true);
else
appendElement(parent, `<div class="level">$key: <span>$value</span></div>`);
);
;
/**
* Toggles the CSS class .fold on the provided element
* @params HTMLElement element on which to toggle the class
* @return undefined
*/
const fold = element => element.classList.toggle('fold');
/**
* Document click listener
*/
document.addEventListener('click', function()
const target = event.target;
const isFoldable = target.classList.contains('parent');
if(isFoldable)
fold(target.parentElement);
);
showObject(obj);
body>.levelposition:relative;left:25%;width:50%.levelbackground:lightgrey;border:2px solid brown;padding:4px;margin:4px;overflow:hidden.level>spancolor:#0366d5.level>.parentcolor:green;display:inline-block;width:100%.foldheight:18px
<script>const createElement=htmlString=>const div=document.createElement('div');div.innerHTML=htmlString;return div.firstChild;const appendElement=(parent,element)=>element=element instanceof HTMLElement?element:createElement(element);return(parent instanceof HTMLElement?parent:document.querySelector(parent)).appendChild(element);const obj = innerObj:other:'yes',note:12,innerObj:other:'no',note:1,name:'one',int:1225,bool:true;</script>
Here it is seen working with a cycle:
let obj = ;
obj['obj'] = obj;
showObject(obj);
body>.levelposition:relative;left:25%;width:50%.levelbackground:lightgrey;border:2px solid brown;padding:4px;margin:4px;overflow:hidden.level>spancolor:#0366d5.level>.parentcolor:green;display:inline-block;width:100%.foldheight:18px
<script>const createElement=htmlString=>const div=document.createElement('div');div.innerHTML=htmlString;return div.firstChild;const appendElement=(parent,element)=>element=element instanceof HTMLElement?element:createElement(element);return(parent instanceof HTMLElement?parent:document.querySelector(parent)).appendChild(element);const showObject=(object,parent='body')=>Object.entries(object).forEach(([key,value])=>if(value&&value.constructor==Object)const element=appendElement(parent,`<div class="level fold"><div class="parent"><span>-</span> $key<div></div>`);element.addEventListener('click',()=>showObject(value,element),once:!0)elseappendElement(parent,`<div class="level">$key: <span>$value</span></div>`));const fold=element=>element.classList.toggle('fold');document.addEventListener('click',function()const target=event.target;const isFoldable=target.classList.contains('parent');if(isFoldable)fold(target.parentElement))</script>
Questions
- What do you think?
- What could be improved (structure, naming, comments, programming style)?
- Any advice?
javascript ecmascript-6
add a comment |Â
up vote
5
down vote
favorite
I wanted to reproduce the type of navigation system found in the console of Firefox and Chrome: you can explore an object's properties by unfolding boxes:
So I'm searching to:
display all of the object's properties (even if they're nested)
being able to fold/unfold the properties if themselves are objects
Element handling
I have written two functions createElement
and appendElement
, I will need them later:
/**
* Creates an HTML element without rendering in the DOM
* @params String htmlString is the HTML string that is created
* @return HTMLElement is the actual object of type HTMLElement
*/
const createElement = htmlString =>
const div = document.createElement('div');
div.innerHTML = htmlString;
return div.firstChild;
/**
* Appends the given element to another element already rendered in the DOM
* @params String or HTMLElement parent is either a CSS String selector or a DOM element
* String or HTMLElement element is either a HTML String or an HTMLElement
* @return HTMLElement the appended child element
*/
const appendElement = (parent, element) =>
element = element instanceof HTMLElement ? element : createElement(element);
return (parent instanceof HTMLElement ? parent : document.querySelector(parent))
.appendChild(element);
Version 1
My initial attempt was to use a recursive approach. Essentially each level would call the function for each of its children which would, in turn, call the function for their own children.
In the end, it would result in having the complete tree displayed on the page.
const showContent = (object, parent) =>
Object.keys(object).forEach(e =>
if(object[e] && object[e].constructor == Object)
showContent(object[e], appendElement(parent, `<div class="level fold"><div class="parent"><span>-</span> $e<div></div>`));
else
appendElement(parent, `<div class="level">$e: <span>$object[e]</span></div>`)
);
// display the object's property
showContent(obj, 'body');
// toggle function to fold/unfold the properties
const toggle = () => event.target.parentElement.classList.toggle('fold');
// listen to click event on each element
document.querySelectorAll('.parent span').forEach(e => e.parentElement.addEventListener('click', toggle));
const showContent = (object, parent) =>
Object.keys(object).forEach(e =>
if(object[e] && object[e].constructor == Object)
showContent(object[e], appendElement(parent, `<div class="level fold"><div class="parent"><span>-</span> $e<div></div>`));
else
appendElement(parent, `<div class="level">$e: <span>$object[e]</span></div>`)
);
// display the object's property
showContent(obj, 'body');
// toggle function to fold/unfold the properties
const toggle = () => event.target.parentElement.classList.toggle('fold');
// listen to click event on each element
document.querySelectorAll('.parent span').forEach(e => e.parentElement.addEventListener('click', toggle));
body>.levelposition:relative;left:25%;width:50%.levelbackground:lightgrey;border:2px solid brown;padding:4px;margin:4px;overflow:hidden.level>spancolor:#0366d5.level>.parentcolor:green;display:inline-block;width:100%.foldheight:18px
<script>const createElement=htmlString=>const div=document.createElement('div');div.innerHTML=htmlString;return div.firstChild;const appendElement=(parent,element)=>element=element instanceof HTMLElement?element:createElement(element);return(parent instanceof HTMLElement?parent:document.querySelector(parent)).appendChild(element);const obj = innerObj:other:'yes',note:12,innerObj:other:'no',note:1,name:'one',int:1225,bool:true;</script>
Here are a few things I didn't like about this code:
there's an event on each property element, also the event listener needs to be set after the element has been added to the DOM. So calling
showContent
before the event handlers doesn't feel natural.this version doesn't support circular structures. For example:
let obj = ;
obj['obj'] = obj;
showContent(obj);will fail...
So this won't work for me. I need something able to handle cycles without too much trouble and that would not require to add an event listener each time a new property is unfolded.
Version 2
I came up with a better version that solved all these problems:
/**
* Shows all the object's properties with a depth of 1
* @params Object object, its first level properties are shown
* String or HTMLElement parent the element in which are displayed the properties
* @return undefined
*/
const showObject = (object, parent='body') =>
Object.entries(object).forEach(([key, value]) =>
if(value && value.constructor == Object)
const element = appendElement(parent, `<div class="level fold"><div class="parent"><span>-</span> $key<div></div>`);
element.addEventListener('click', () => showObject(value, element), once: true);
else
appendElement(parent, `<div class="level">$key: <span>$value</span></div>`);
);
;
/**
* Toggles the CSS class .fold on the provided element
* @params HTMLElement element on which to toggle the class
* @return undefined
*/
const fold = element => element.classList.toggle('fold');
/**
* Document click listener
*/
document.addEventListener('click', function()
const target = event.target;
const isFoldable = target.classList.contains('parent');
if(isFoldable)
fold(target.parentElement);
);
/**
* Shows all the object's properties with a depth of 1
* @params Object object, its first level properties are shown
* String or HTMLElement parent the element in which are displayed the properties
* @return undefined
*/
const showObject = (object, parent='body') =>
Object.entries(object).forEach(([key, value]) =>
if(value && value.constructor == Object)
const element = appendElement(parent, `<div class="level fold"><div class="parent"><span>-</span> $key<div></div>`);
element.addEventListener('click', () => showObject(value, element), once: true);
else
appendElement(parent, `<div class="level">$key: <span>$value</span></div>`);
);
;
/**
* Toggles the CSS class .fold on the provided element
* @params HTMLElement element on which to toggle the class
* @return undefined
*/
const fold = element => element.classList.toggle('fold');
/**
* Document click listener
*/
document.addEventListener('click', function()
const target = event.target;
const isFoldable = target.classList.contains('parent');
if(isFoldable)
fold(target.parentElement);
);
showObject(obj);
body>.levelposition:relative;left:25%;width:50%.levelbackground:lightgrey;border:2px solid brown;padding:4px;margin:4px;overflow:hidden.level>spancolor:#0366d5.level>.parentcolor:green;display:inline-block;width:100%.foldheight:18px
<script>const createElement=htmlString=>const div=document.createElement('div');div.innerHTML=htmlString;return div.firstChild;const appendElement=(parent,element)=>element=element instanceof HTMLElement?element:createElement(element);return(parent instanceof HTMLElement?parent:document.querySelector(parent)).appendChild(element);const obj = innerObj:other:'yes',note:12,innerObj:other:'no',note:1,name:'one',int:1225,bool:true;</script>
Here it is seen working with a cycle:
let obj = ;
obj['obj'] = obj;
showObject(obj);
body>.levelposition:relative;left:25%;width:50%.levelbackground:lightgrey;border:2px solid brown;padding:4px;margin:4px;overflow:hidden.level>spancolor:#0366d5.level>.parentcolor:green;display:inline-block;width:100%.foldheight:18px
<script>const createElement=htmlString=>const div=document.createElement('div');div.innerHTML=htmlString;return div.firstChild;const appendElement=(parent,element)=>element=element instanceof HTMLElement?element:createElement(element);return(parent instanceof HTMLElement?parent:document.querySelector(parent)).appendChild(element);const showObject=(object,parent='body')=>Object.entries(object).forEach(([key,value])=>if(value&&value.constructor==Object)const element=appendElement(parent,`<div class="level fold"><div class="parent"><span>-</span> $key<div></div>`);element.addEventListener('click',()=>showObject(value,element),once:!0)elseappendElement(parent,`<div class="level">$key: <span>$value</span></div>`));const fold=element=>element.classList.toggle('fold');document.addEventListener('click',function()const target=event.target;const isFoldable=target.classList.contains('parent');if(isFoldable)fold(target.parentElement))</script>
Questions
- What do you think?
- What could be improved (structure, naming, comments, programming style)?
- Any advice?
javascript ecmascript-6
add a comment |Â
up vote
5
down vote
favorite
up vote
5
down vote
favorite
I wanted to reproduce the type of navigation system found in the console of Firefox and Chrome: you can explore an object's properties by unfolding boxes:
So I'm searching to:
display all of the object's properties (even if they're nested)
being able to fold/unfold the properties if themselves are objects
Element handling
I have written two functions createElement
and appendElement
, I will need them later:
/**
* Creates an HTML element without rendering in the DOM
* @params String htmlString is the HTML string that is created
* @return HTMLElement is the actual object of type HTMLElement
*/
const createElement = htmlString =>
const div = document.createElement('div');
div.innerHTML = htmlString;
return div.firstChild;
/**
* Appends the given element to another element already rendered in the DOM
* @params String or HTMLElement parent is either a CSS String selector or a DOM element
* String or HTMLElement element is either a HTML String or an HTMLElement
* @return HTMLElement the appended child element
*/
const appendElement = (parent, element) =>
element = element instanceof HTMLElement ? element : createElement(element);
return (parent instanceof HTMLElement ? parent : document.querySelector(parent))
.appendChild(element);
Version 1
My initial attempt was to use a recursive approach. Essentially each level would call the function for each of its children which would, in turn, call the function for their own children.
In the end, it would result in having the complete tree displayed on the page.
const showContent = (object, parent) =>
Object.keys(object).forEach(e =>
if(object[e] && object[e].constructor == Object)
showContent(object[e], appendElement(parent, `<div class="level fold"><div class="parent"><span>-</span> $e<div></div>`));
else
appendElement(parent, `<div class="level">$e: <span>$object[e]</span></div>`)
);
// display the object's property
showContent(obj, 'body');
// toggle function to fold/unfold the properties
const toggle = () => event.target.parentElement.classList.toggle('fold');
// listen to click event on each element
document.querySelectorAll('.parent span').forEach(e => e.parentElement.addEventListener('click', toggle));
const showContent = (object, parent) =>
Object.keys(object).forEach(e =>
if(object[e] && object[e].constructor == Object)
showContent(object[e], appendElement(parent, `<div class="level fold"><div class="parent"><span>-</span> $e<div></div>`));
else
appendElement(parent, `<div class="level">$e: <span>$object[e]</span></div>`)
);
// display the object's property
showContent(obj, 'body');
// toggle function to fold/unfold the properties
const toggle = () => event.target.parentElement.classList.toggle('fold');
// listen to click event on each element
document.querySelectorAll('.parent span').forEach(e => e.parentElement.addEventListener('click', toggle));
body>.levelposition:relative;left:25%;width:50%.levelbackground:lightgrey;border:2px solid brown;padding:4px;margin:4px;overflow:hidden.level>spancolor:#0366d5.level>.parentcolor:green;display:inline-block;width:100%.foldheight:18px
<script>const createElement=htmlString=>const div=document.createElement('div');div.innerHTML=htmlString;return div.firstChild;const appendElement=(parent,element)=>element=element instanceof HTMLElement?element:createElement(element);return(parent instanceof HTMLElement?parent:document.querySelector(parent)).appendChild(element);const obj = innerObj:other:'yes',note:12,innerObj:other:'no',note:1,name:'one',int:1225,bool:true;</script>
Here are a few things I didn't like about this code:
there's an event on each property element, also the event listener needs to be set after the element has been added to the DOM. So calling
showContent
before the event handlers doesn't feel natural.this version doesn't support circular structures. For example:
let obj = ;
obj['obj'] = obj;
showContent(obj);will fail...
So this won't work for me. I need something able to handle cycles without too much trouble and that would not require to add an event listener each time a new property is unfolded.
Version 2
I came up with a better version that solved all these problems:
/**
* Shows all the object's properties with a depth of 1
* @params Object object, its first level properties are shown
* String or HTMLElement parent the element in which are displayed the properties
* @return undefined
*/
const showObject = (object, parent='body') =>
Object.entries(object).forEach(([key, value]) =>
if(value && value.constructor == Object)
const element = appendElement(parent, `<div class="level fold"><div class="parent"><span>-</span> $key<div></div>`);
element.addEventListener('click', () => showObject(value, element), once: true);
else
appendElement(parent, `<div class="level">$key: <span>$value</span></div>`);
);
;
/**
* Toggles the CSS class .fold on the provided element
* @params HTMLElement element on which to toggle the class
* @return undefined
*/
const fold = element => element.classList.toggle('fold');
/**
* Document click listener
*/
document.addEventListener('click', function()
const target = event.target;
const isFoldable = target.classList.contains('parent');
if(isFoldable)
fold(target.parentElement);
);
/**
* Shows all the object's properties with a depth of 1
* @params Object object, its first level properties are shown
* String or HTMLElement parent the element in which are displayed the properties
* @return undefined
*/
const showObject = (object, parent='body') =>
Object.entries(object).forEach(([key, value]) =>
if(value && value.constructor == Object)
const element = appendElement(parent, `<div class="level fold"><div class="parent"><span>-</span> $key<div></div>`);
element.addEventListener('click', () => showObject(value, element), once: true);
else
appendElement(parent, `<div class="level">$key: <span>$value</span></div>`);
);
;
/**
* Toggles the CSS class .fold on the provided element
* @params HTMLElement element on which to toggle the class
* @return undefined
*/
const fold = element => element.classList.toggle('fold');
/**
* Document click listener
*/
document.addEventListener('click', function()
const target = event.target;
const isFoldable = target.classList.contains('parent');
if(isFoldable)
fold(target.parentElement);
);
showObject(obj);
body>.levelposition:relative;left:25%;width:50%.levelbackground:lightgrey;border:2px solid brown;padding:4px;margin:4px;overflow:hidden.level>spancolor:#0366d5.level>.parentcolor:green;display:inline-block;width:100%.foldheight:18px
<script>const createElement=htmlString=>const div=document.createElement('div');div.innerHTML=htmlString;return div.firstChild;const appendElement=(parent,element)=>element=element instanceof HTMLElement?element:createElement(element);return(parent instanceof HTMLElement?parent:document.querySelector(parent)).appendChild(element);const obj = innerObj:other:'yes',note:12,innerObj:other:'no',note:1,name:'one',int:1225,bool:true;</script>
Here it is seen working with a cycle:
let obj = ;
obj['obj'] = obj;
showObject(obj);
body>.levelposition:relative;left:25%;width:50%.levelbackground:lightgrey;border:2px solid brown;padding:4px;margin:4px;overflow:hidden.level>spancolor:#0366d5.level>.parentcolor:green;display:inline-block;width:100%.foldheight:18px
<script>const createElement=htmlString=>const div=document.createElement('div');div.innerHTML=htmlString;return div.firstChild;const appendElement=(parent,element)=>element=element instanceof HTMLElement?element:createElement(element);return(parent instanceof HTMLElement?parent:document.querySelector(parent)).appendChild(element);const showObject=(object,parent='body')=>Object.entries(object).forEach(([key,value])=>if(value&&value.constructor==Object)const element=appendElement(parent,`<div class="level fold"><div class="parent"><span>-</span> $key<div></div>`);element.addEventListener('click',()=>showObject(value,element),once:!0)elseappendElement(parent,`<div class="level">$key: <span>$value</span></div>`));const fold=element=>element.classList.toggle('fold');document.addEventListener('click',function()const target=event.target;const isFoldable=target.classList.contains('parent');if(isFoldable)fold(target.parentElement))</script>
Questions
- What do you think?
- What could be improved (structure, naming, comments, programming style)?
- Any advice?
javascript ecmascript-6
I wanted to reproduce the type of navigation system found in the console of Firefox and Chrome: you can explore an object's properties by unfolding boxes:
So I'm searching to:
display all of the object's properties (even if they're nested)
being able to fold/unfold the properties if themselves are objects
Element handling
I have written two functions createElement
and appendElement
, I will need them later:
/**
* Creates an HTML element without rendering in the DOM
* @params String htmlString is the HTML string that is created
* @return HTMLElement is the actual object of type HTMLElement
*/
const createElement = htmlString =>
const div = document.createElement('div');
div.innerHTML = htmlString;
return div.firstChild;
/**
* Appends the given element to another element already rendered in the DOM
* @params String or HTMLElement parent is either a CSS String selector or a DOM element
* String or HTMLElement element is either a HTML String or an HTMLElement
* @return HTMLElement the appended child element
*/
const appendElement = (parent, element) =>
element = element instanceof HTMLElement ? element : createElement(element);
return (parent instanceof HTMLElement ? parent : document.querySelector(parent))
.appendChild(element);
Version 1
My initial attempt was to use a recursive approach. Essentially each level would call the function for each of its children which would, in turn, call the function for their own children.
In the end, it would result in having the complete tree displayed on the page.
const showContent = (object, parent) =>
Object.keys(object).forEach(e =>
if(object[e] && object[e].constructor == Object)
showContent(object[e], appendElement(parent, `<div class="level fold"><div class="parent"><span>-</span> $e<div></div>`));
else
appendElement(parent, `<div class="level">$e: <span>$object[e]</span></div>`)
);
// display the object's property
showContent(obj, 'body');
// toggle function to fold/unfold the properties
const toggle = () => event.target.parentElement.classList.toggle('fold');
// listen to click event on each element
document.querySelectorAll('.parent span').forEach(e => e.parentElement.addEventListener('click', toggle));
const showContent = (object, parent) =>
Object.keys(object).forEach(e =>
if(object[e] && object[e].constructor == Object)
showContent(object[e], appendElement(parent, `<div class="level fold"><div class="parent"><span>-</span> $e<div></div>`));
else
appendElement(parent, `<div class="level">$e: <span>$object[e]</span></div>`)
);
// display the object's property
showContent(obj, 'body');
// toggle function to fold/unfold the properties
const toggle = () => event.target.parentElement.classList.toggle('fold');
// listen to click event on each element
document.querySelectorAll('.parent span').forEach(e => e.parentElement.addEventListener('click', toggle));
body>.levelposition:relative;left:25%;width:50%.levelbackground:lightgrey;border:2px solid brown;padding:4px;margin:4px;overflow:hidden.level>spancolor:#0366d5.level>.parentcolor:green;display:inline-block;width:100%.foldheight:18px
<script>const createElement=htmlString=>const div=document.createElement('div');div.innerHTML=htmlString;return div.firstChild;const appendElement=(parent,element)=>element=element instanceof HTMLElement?element:createElement(element);return(parent instanceof HTMLElement?parent:document.querySelector(parent)).appendChild(element);const obj = innerObj:other:'yes',note:12,innerObj:other:'no',note:1,name:'one',int:1225,bool:true;</script>
Here are a few things I didn't like about this code:
there's an event on each property element, also the event listener needs to be set after the element has been added to the DOM. So calling
showContent
before the event handlers doesn't feel natural.this version doesn't support circular structures. For example:
let obj = ;
obj['obj'] = obj;
showContent(obj);will fail...
So this won't work for me. I need something able to handle cycles without too much trouble and that would not require to add an event listener each time a new property is unfolded.
Version 2
I came up with a better version that solved all these problems:
/**
* Shows all the object's properties with a depth of 1
* @params Object object, its first level properties are shown
* String or HTMLElement parent the element in which are displayed the properties
* @return undefined
*/
const showObject = (object, parent='body') =>
Object.entries(object).forEach(([key, value]) =>
if(value && value.constructor == Object)
const element = appendElement(parent, `<div class="level fold"><div class="parent"><span>-</span> $key<div></div>`);
element.addEventListener('click', () => showObject(value, element), once: true);
else
appendElement(parent, `<div class="level">$key: <span>$value</span></div>`);
);
;
/**
* Toggles the CSS class .fold on the provided element
* @params HTMLElement element on which to toggle the class
* @return undefined
*/
const fold = element => element.classList.toggle('fold');
/**
* Document click listener
*/
document.addEventListener('click', function()
const target = event.target;
const isFoldable = target.classList.contains('parent');
if(isFoldable)
fold(target.parentElement);
);
/**
* Shows all the object's properties with a depth of 1
* @params Object object, its first level properties are shown
* String or HTMLElement parent the element in which are displayed the properties
* @return undefined
*/
const showObject = (object, parent='body') =>
Object.entries(object).forEach(([key, value]) =>
if(value && value.constructor == Object)
const element = appendElement(parent, `<div class="level fold"><div class="parent"><span>-</span> $key<div></div>`);
element.addEventListener('click', () => showObject(value, element), once: true);
else
appendElement(parent, `<div class="level">$key: <span>$value</span></div>`);
);
;
/**
* Toggles the CSS class .fold on the provided element
* @params HTMLElement element on which to toggle the class
* @return undefined
*/
const fold = element => element.classList.toggle('fold');
/**
* Document click listener
*/
document.addEventListener('click', function()
const target = event.target;
const isFoldable = target.classList.contains('parent');
if(isFoldable)
fold(target.parentElement);
);
showObject(obj);
body>.levelposition:relative;left:25%;width:50%.levelbackground:lightgrey;border:2px solid brown;padding:4px;margin:4px;overflow:hidden.level>spancolor:#0366d5.level>.parentcolor:green;display:inline-block;width:100%.foldheight:18px
<script>const createElement=htmlString=>const div=document.createElement('div');div.innerHTML=htmlString;return div.firstChild;const appendElement=(parent,element)=>element=element instanceof HTMLElement?element:createElement(element);return(parent instanceof HTMLElement?parent:document.querySelector(parent)).appendChild(element);const obj = innerObj:other:'yes',note:12,innerObj:other:'no',note:1,name:'one',int:1225,bool:true;</script>
Here it is seen working with a cycle:
let obj = ;
obj['obj'] = obj;
showObject(obj);
body>.levelposition:relative;left:25%;width:50%.levelbackground:lightgrey;border:2px solid brown;padding:4px;margin:4px;overflow:hidden.level>spancolor:#0366d5.level>.parentcolor:green;display:inline-block;width:100%.foldheight:18px
<script>const createElement=htmlString=>const div=document.createElement('div');div.innerHTML=htmlString;return div.firstChild;const appendElement=(parent,element)=>element=element instanceof HTMLElement?element:createElement(element);return(parent instanceof HTMLElement?parent:document.querySelector(parent)).appendChild(element);const showObject=(object,parent='body')=>Object.entries(object).forEach(([key,value])=>if(value&&value.constructor==Object)const element=appendElement(parent,`<div class="level fold"><div class="parent"><span>-</span> $key<div></div>`);element.addEventListener('click',()=>showObject(value,element),once:!0)elseappendElement(parent,`<div class="level">$key: <span>$value</span></div>`));const fold=element=>element.classList.toggle('fold');document.addEventListener('click',function()const target=event.target;const isFoldable=target.classList.contains('parent');if(isFoldable)fold(target.parentElement))</script>
Questions
- What do you think?
- What could be improved (structure, naming, comments, programming style)?
- Any advice?
const showContent = (object, parent) =>
Object.keys(object).forEach(e =>
if(object[e] && object[e].constructor == Object)
showContent(object[e], appendElement(parent, `<div class="level fold"><div class="parent"><span>-</span> $e<div></div>`));
else
appendElement(parent, `<div class="level">$e: <span>$object[e]</span></div>`)
);
// display the object's property
showContent(obj, 'body');
// toggle function to fold/unfold the properties
const toggle = () => event.target.parentElement.classList.toggle('fold');
// listen to click event on each element
document.querySelectorAll('.parent span').forEach(e => e.parentElement.addEventListener('click', toggle));
body>.levelposition:relative;left:25%;width:50%.levelbackground:lightgrey;border:2px solid brown;padding:4px;margin:4px;overflow:hidden.level>spancolor:#0366d5.level>.parentcolor:green;display:inline-block;width:100%.foldheight:18px
<script>const createElement=htmlString=>const div=document.createElement('div');div.innerHTML=htmlString;return div.firstChild;const appendElement=(parent,element)=>element=element instanceof HTMLElement?element:createElement(element);return(parent instanceof HTMLElement?parent:document.querySelector(parent)).appendChild(element);const obj = innerObj:other:'yes',note:12,innerObj:other:'no',note:1,name:'one',int:1225,bool:true;</script>
const showContent = (object, parent) =>
Object.keys(object).forEach(e =>
if(object[e] && object[e].constructor == Object)
showContent(object[e], appendElement(parent, `<div class="level fold"><div class="parent"><span>-</span> $e<div></div>`));
else
appendElement(parent, `<div class="level">$e: <span>$object[e]</span></div>`)
);
// display the object's property
showContent(obj, 'body');
// toggle function to fold/unfold the properties
const toggle = () => event.target.parentElement.classList.toggle('fold');
// listen to click event on each element
document.querySelectorAll('.parent span').forEach(e => e.parentElement.addEventListener('click', toggle));
body>.levelposition:relative;left:25%;width:50%.levelbackground:lightgrey;border:2px solid brown;padding:4px;margin:4px;overflow:hidden.level>spancolor:#0366d5.level>.parentcolor:green;display:inline-block;width:100%.foldheight:18px
<script>const createElement=htmlString=>const div=document.createElement('div');div.innerHTML=htmlString;return div.firstChild;const appendElement=(parent,element)=>element=element instanceof HTMLElement?element:createElement(element);return(parent instanceof HTMLElement?parent:document.querySelector(parent)).appendChild(element);const obj = innerObj:other:'yes',note:12,innerObj:other:'no',note:1,name:'one',int:1225,bool:true;</script>
/**
* Shows all the object's properties with a depth of 1
* @params Object object, its first level properties are shown
* String or HTMLElement parent the element in which are displayed the properties
* @return undefined
*/
const showObject = (object, parent='body') =>
Object.entries(object).forEach(([key, value]) =>
if(value && value.constructor == Object)
const element = appendElement(parent, `<div class="level fold"><div class="parent"><span>-</span> $key<div></div>`);
element.addEventListener('click', () => showObject(value, element), once: true);
else
appendElement(parent, `<div class="level">$key: <span>$value</span></div>`);
);
;
/**
* Toggles the CSS class .fold on the provided element
* @params HTMLElement element on which to toggle the class
* @return undefined
*/
const fold = element => element.classList.toggle('fold');
/**
* Document click listener
*/
document.addEventListener('click', function()
const target = event.target;
const isFoldable = target.classList.contains('parent');
if(isFoldable)
fold(target.parentElement);
);
showObject(obj);
body>.levelposition:relative;left:25%;width:50%.levelbackground:lightgrey;border:2px solid brown;padding:4px;margin:4px;overflow:hidden.level>spancolor:#0366d5.level>.parentcolor:green;display:inline-block;width:100%.foldheight:18px
<script>const createElement=htmlString=>const div=document.createElement('div');div.innerHTML=htmlString;return div.firstChild;const appendElement=(parent,element)=>element=element instanceof HTMLElement?element:createElement(element);return(parent instanceof HTMLElement?parent:document.querySelector(parent)).appendChild(element);const obj = innerObj:other:'yes',note:12,innerObj:other:'no',note:1,name:'one',int:1225,bool:true;</script>
/**
* Shows all the object's properties with a depth of 1
* @params Object object, its first level properties are shown
* String or HTMLElement parent the element in which are displayed the properties
* @return undefined
*/
const showObject = (object, parent='body') =>
Object.entries(object).forEach(([key, value]) =>
if(value && value.constructor == Object)
const element = appendElement(parent, `<div class="level fold"><div class="parent"><span>-</span> $key<div></div>`);
element.addEventListener('click', () => showObject(value, element), once: true);
else
appendElement(parent, `<div class="level">$key: <span>$value</span></div>`);
);
;
/**
* Toggles the CSS class .fold on the provided element
* @params HTMLElement element on which to toggle the class
* @return undefined
*/
const fold = element => element.classList.toggle('fold');
/**
* Document click listener
*/
document.addEventListener('click', function()
const target = event.target;
const isFoldable = target.classList.contains('parent');
if(isFoldable)
fold(target.parentElement);
);
showObject(obj);
body>.levelposition:relative;left:25%;width:50%.levelbackground:lightgrey;border:2px solid brown;padding:4px;margin:4px;overflow:hidden.level>spancolor:#0366d5.level>.parentcolor:green;display:inline-block;width:100%.foldheight:18px
<script>const createElement=htmlString=>const div=document.createElement('div');div.innerHTML=htmlString;return div.firstChild;const appendElement=(parent,element)=>element=element instanceof HTMLElement?element:createElement(element);return(parent instanceof HTMLElement?parent:document.querySelector(parent)).appendChild(element);const obj = innerObj:other:'yes',note:12,innerObj:other:'no',note:1,name:'one',int:1225,bool:true;</script>
let obj = ;
obj['obj'] = obj;
showObject(obj);
body>.levelposition:relative;left:25%;width:50%.levelbackground:lightgrey;border:2px solid brown;padding:4px;margin:4px;overflow:hidden.level>spancolor:#0366d5.level>.parentcolor:green;display:inline-block;width:100%.foldheight:18px
<script>const createElement=htmlString=>const div=document.createElement('div');div.innerHTML=htmlString;return div.firstChild;const appendElement=(parent,element)=>element=element instanceof HTMLElement?element:createElement(element);return(parent instanceof HTMLElement?parent:document.querySelector(parent)).appendChild(element);const showObject=(object,parent='body')=>Object.entries(object).forEach(([key,value])=>if(value&&value.constructor==Object)const element=appendElement(parent,`<div class="level fold"><div class="parent"><span>-</span> $key<div></div>`);element.addEventListener('click',()=>showObject(value,element),once:!0)elseappendElement(parent,`<div class="level">$key: <span>$value</span></div>`));const fold=element=>element.classList.toggle('fold');document.addEventListener('click',function()const target=event.target;const isFoldable=target.classList.contains('parent');if(isFoldable)fold(target.parentElement))</script>
let obj = ;
obj['obj'] = obj;
showObject(obj);
body>.levelposition:relative;left:25%;width:50%.levelbackground:lightgrey;border:2px solid brown;padding:4px;margin:4px;overflow:hidden.level>spancolor:#0366d5.level>.parentcolor:green;display:inline-block;width:100%.foldheight:18px
<script>const createElement=htmlString=>const div=document.createElement('div');div.innerHTML=htmlString;return div.firstChild;const appendElement=(parent,element)=>element=element instanceof HTMLElement?element:createElement(element);return(parent instanceof HTMLElement?parent:document.querySelector(parent)).appendChild(element);const showObject=(object,parent='body')=>Object.entries(object).forEach(([key,value])=>if(value&&value.constructor==Object)const element=appendElement(parent,`<div class="level fold"><div class="parent"><span>-</span> $key<div></div>`);element.addEventListener('click',()=>showObject(value,element),once:!0)elseappendElement(parent,`<div class="level">$key: <span>$value</span></div>`));const fold=element=>element.classList.toggle('fold');document.addEventListener('click',function()const target=event.target;const isFoldable=target.classList.contains('parent');if(isFoldable)fold(target.parentElement))</script>
javascript ecmascript-6
edited Jul 2 at 22:13
asked Jun 23 at 20:29
Ivan
33511
33511
add a comment |Â
add a comment |Â
1 Answer
1
active
oldest
votes
up vote
3
down vote
accepted
Very neat project! Your code already looks pretty clean to me. I especially like the optimization of only running showObject
once for each expansion.
Bug: If my object is
a: '<b>Bold</b>'
, the text displayed for propertya
will beBold
, not<b>Bold</b>
. This is a great example of why building HTML with strings is a bad idea. The<template>
element can be used to define an HTML structure and replicate it multiple times.Missing implementation: The Chrome devtools handle Sets, Maps, and other data structures in a very nice way, making it possible to peek inside them. Your implementation will just display them as strings (e.g.
[object Set]
). If you are looking to extend this code, I'd recommend handling Sets, Maps, and Dates.You might want to consider using the
<details>
element instead of a custom foldable structure.In general, I avoid allowing multiple types of parameters. Instead of taking a string or an element for
showObject
's parent, I'd only take an element and change the default argument todocument.body
.Enhancement: Getters should not be evaluated until explicitly requested. This lets the user avoid evaluating getters which mutate the state (yes, unfortunately some people do this...). You can check if a key is a getter with
Reflect.getOwnPropertyDescriptor()
Demo (open inner, obj, inner):
let obj =
inner:
i: 0,
get x() return obj.inner.i++
;
obj.obj = obj;
showObject(obj);body>.levelposition:relative;left:25%;width:50%.levelbackground:lightgrey;border:2px solid brown;padding:4px;margin:4px;overflow:hidden.level>spancolor:#0366d5.level>.parentcolor:green;display:inline-block;width:100%.foldheight:18px
<script>const createElement=htmlString=>const div=document.createElement('div');div.innerHTML=htmlString;return div.firstChild;const appendElement=(parent,element)=>element=element instanceof HTMLElement?element:createElement(element);return(parent instanceof HTMLElement?parent:document.querySelector(parent)).appendChild(element);const showObject=(object,parent='body')=>Object.entries(object).forEach(([key,value])=>if(value&&value.constructor==Object)const element=appendElement(parent,`<div class="level fold"><div class="parent"><span>-</span> $key<div></div>`);element.addEventListener('click',()=>showObject(value,element),once:!0)elseappendElement(parent,`<div class="level">$key: <span>$value</span></div>`));const fold=element=>element.classList.toggle('fold');document.addEventListener('click',function()const target=event.target;const isFoldable=target.classList.contains('parent');if(isFoldable)fold(target.parentElement))</script>
Since this seemed very short, I decided to provide a simple example of one way to fix the HTML injection. I'll admit - in this case templates might be slightly overkill, but when working with even slightly more complex HTML structures, templates can be a lifesaver.
const showObject = (object, parent = document.body) =>
const keyValueTemplate = document.getElementById('keyValue');
const folderTemplate = document.getElementById('folder');
Object.entries(object).forEach(([key, value]) =>
if (value && value.constructor == Object)
// Since this structure is really simple, just create the elements.
const element = document.createElement('details');
const summary = element.appendChild(document.createElement('summary'));
summary.textContent = key;
element.addEventListener('toggle', () =>
showObject(value, element)
, once: true );
parent.appendChild(element);
else
// Use a template since the structure is somewhat complex.
const element = document.importNode(keyValueTemplate.content, true);
element.querySelector('.key').textContent = key;
element.querySelector('.value').textContent = value;
parent.appendChild(element);
);
;
showObject( a: "<b>Hi</b>", b: c: 123, d: true );
.property, details
background: lightgrey;
border: 2px solid brown;
padding: 4px;
margin: 4px;
overflow: hidden;
.value
color: #0366d5;
summary
color: green;
<template id="keyValue">
<div class="property">
<span class="key"></span>:
<span class="value"></span>
</div>
</template>
Hello @Guerrit0 and thank you very much for reviewing my project. I've learnt a lot reading your answer. There are a lot of things I have improved: I have actually published a follow up question just now with my new version. I haven't implemented parsing ofSet
,Map
andDate
objects nor the verification of getters, but it will come... Thanks again. Ivan
â Ivan
Jul 2 at 22:11
add a comment |Â
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
3
down vote
accepted
Very neat project! Your code already looks pretty clean to me. I especially like the optimization of only running showObject
once for each expansion.
Bug: If my object is
a: '<b>Bold</b>'
, the text displayed for propertya
will beBold
, not<b>Bold</b>
. This is a great example of why building HTML with strings is a bad idea. The<template>
element can be used to define an HTML structure and replicate it multiple times.Missing implementation: The Chrome devtools handle Sets, Maps, and other data structures in a very nice way, making it possible to peek inside them. Your implementation will just display them as strings (e.g.
[object Set]
). If you are looking to extend this code, I'd recommend handling Sets, Maps, and Dates.You might want to consider using the
<details>
element instead of a custom foldable structure.In general, I avoid allowing multiple types of parameters. Instead of taking a string or an element for
showObject
's parent, I'd only take an element and change the default argument todocument.body
.Enhancement: Getters should not be evaluated until explicitly requested. This lets the user avoid evaluating getters which mutate the state (yes, unfortunately some people do this...). You can check if a key is a getter with
Reflect.getOwnPropertyDescriptor()
Demo (open inner, obj, inner):
let obj =
inner:
i: 0,
get x() return obj.inner.i++
;
obj.obj = obj;
showObject(obj);body>.levelposition:relative;left:25%;width:50%.levelbackground:lightgrey;border:2px solid brown;padding:4px;margin:4px;overflow:hidden.level>spancolor:#0366d5.level>.parentcolor:green;display:inline-block;width:100%.foldheight:18px
<script>const createElement=htmlString=>const div=document.createElement('div');div.innerHTML=htmlString;return div.firstChild;const appendElement=(parent,element)=>element=element instanceof HTMLElement?element:createElement(element);return(parent instanceof HTMLElement?parent:document.querySelector(parent)).appendChild(element);const showObject=(object,parent='body')=>Object.entries(object).forEach(([key,value])=>if(value&&value.constructor==Object)const element=appendElement(parent,`<div class="level fold"><div class="parent"><span>-</span> $key<div></div>`);element.addEventListener('click',()=>showObject(value,element),once:!0)elseappendElement(parent,`<div class="level">$key: <span>$value</span></div>`));const fold=element=>element.classList.toggle('fold');document.addEventListener('click',function()const target=event.target;const isFoldable=target.classList.contains('parent');if(isFoldable)fold(target.parentElement))</script>
Since this seemed very short, I decided to provide a simple example of one way to fix the HTML injection. I'll admit - in this case templates might be slightly overkill, but when working with even slightly more complex HTML structures, templates can be a lifesaver.
const showObject = (object, parent = document.body) =>
const keyValueTemplate = document.getElementById('keyValue');
const folderTemplate = document.getElementById('folder');
Object.entries(object).forEach(([key, value]) =>
if (value && value.constructor == Object)
// Since this structure is really simple, just create the elements.
const element = document.createElement('details');
const summary = element.appendChild(document.createElement('summary'));
summary.textContent = key;
element.addEventListener('toggle', () =>
showObject(value, element)
, once: true );
parent.appendChild(element);
else
// Use a template since the structure is somewhat complex.
const element = document.importNode(keyValueTemplate.content, true);
element.querySelector('.key').textContent = key;
element.querySelector('.value').textContent = value;
parent.appendChild(element);
);
;
showObject( a: "<b>Hi</b>", b: c: 123, d: true );
.property, details
background: lightgrey;
border: 2px solid brown;
padding: 4px;
margin: 4px;
overflow: hidden;
.value
color: #0366d5;
summary
color: green;
<template id="keyValue">
<div class="property">
<span class="key"></span>:
<span class="value"></span>
</div>
</template>
Hello @Guerrit0 and thank you very much for reviewing my project. I've learnt a lot reading your answer. There are a lot of things I have improved: I have actually published a follow up question just now with my new version. I haven't implemented parsing ofSet
,Map
andDate
objects nor the verification of getters, but it will come... Thanks again. Ivan
â Ivan
Jul 2 at 22:11
add a comment |Â
up vote
3
down vote
accepted
Very neat project! Your code already looks pretty clean to me. I especially like the optimization of only running showObject
once for each expansion.
Bug: If my object is
a: '<b>Bold</b>'
, the text displayed for propertya
will beBold
, not<b>Bold</b>
. This is a great example of why building HTML with strings is a bad idea. The<template>
element can be used to define an HTML structure and replicate it multiple times.Missing implementation: The Chrome devtools handle Sets, Maps, and other data structures in a very nice way, making it possible to peek inside them. Your implementation will just display them as strings (e.g.
[object Set]
). If you are looking to extend this code, I'd recommend handling Sets, Maps, and Dates.You might want to consider using the
<details>
element instead of a custom foldable structure.In general, I avoid allowing multiple types of parameters. Instead of taking a string or an element for
showObject
's parent, I'd only take an element and change the default argument todocument.body
.Enhancement: Getters should not be evaluated until explicitly requested. This lets the user avoid evaluating getters which mutate the state (yes, unfortunately some people do this...). You can check if a key is a getter with
Reflect.getOwnPropertyDescriptor()
Demo (open inner, obj, inner):
let obj =
inner:
i: 0,
get x() return obj.inner.i++
;
obj.obj = obj;
showObject(obj);body>.levelposition:relative;left:25%;width:50%.levelbackground:lightgrey;border:2px solid brown;padding:4px;margin:4px;overflow:hidden.level>spancolor:#0366d5.level>.parentcolor:green;display:inline-block;width:100%.foldheight:18px
<script>const createElement=htmlString=>const div=document.createElement('div');div.innerHTML=htmlString;return div.firstChild;const appendElement=(parent,element)=>element=element instanceof HTMLElement?element:createElement(element);return(parent instanceof HTMLElement?parent:document.querySelector(parent)).appendChild(element);const showObject=(object,parent='body')=>Object.entries(object).forEach(([key,value])=>if(value&&value.constructor==Object)const element=appendElement(parent,`<div class="level fold"><div class="parent"><span>-</span> $key<div></div>`);element.addEventListener('click',()=>showObject(value,element),once:!0)elseappendElement(parent,`<div class="level">$key: <span>$value</span></div>`));const fold=element=>element.classList.toggle('fold');document.addEventListener('click',function()const target=event.target;const isFoldable=target.classList.contains('parent');if(isFoldable)fold(target.parentElement))</script>
Since this seemed very short, I decided to provide a simple example of one way to fix the HTML injection. I'll admit - in this case templates might be slightly overkill, but when working with even slightly more complex HTML structures, templates can be a lifesaver.
const showObject = (object, parent = document.body) =>
const keyValueTemplate = document.getElementById('keyValue');
const folderTemplate = document.getElementById('folder');
Object.entries(object).forEach(([key, value]) =>
if (value && value.constructor == Object)
// Since this structure is really simple, just create the elements.
const element = document.createElement('details');
const summary = element.appendChild(document.createElement('summary'));
summary.textContent = key;
element.addEventListener('toggle', () =>
showObject(value, element)
, once: true );
parent.appendChild(element);
else
// Use a template since the structure is somewhat complex.
const element = document.importNode(keyValueTemplate.content, true);
element.querySelector('.key').textContent = key;
element.querySelector('.value').textContent = value;
parent.appendChild(element);
);
;
showObject( a: "<b>Hi</b>", b: c: 123, d: true );
.property, details
background: lightgrey;
border: 2px solid brown;
padding: 4px;
margin: 4px;
overflow: hidden;
.value
color: #0366d5;
summary
color: green;
<template id="keyValue">
<div class="property">
<span class="key"></span>:
<span class="value"></span>
</div>
</template>
Hello @Guerrit0 and thank you very much for reviewing my project. I've learnt a lot reading your answer. There are a lot of things I have improved: I have actually published a follow up question just now with my new version. I haven't implemented parsing ofSet
,Map
andDate
objects nor the verification of getters, but it will come... Thanks again. Ivan
â Ivan
Jul 2 at 22:11
add a comment |Â
up vote
3
down vote
accepted
up vote
3
down vote
accepted
Very neat project! Your code already looks pretty clean to me. I especially like the optimization of only running showObject
once for each expansion.
Bug: If my object is
a: '<b>Bold</b>'
, the text displayed for propertya
will beBold
, not<b>Bold</b>
. This is a great example of why building HTML with strings is a bad idea. The<template>
element can be used to define an HTML structure and replicate it multiple times.Missing implementation: The Chrome devtools handle Sets, Maps, and other data structures in a very nice way, making it possible to peek inside them. Your implementation will just display them as strings (e.g.
[object Set]
). If you are looking to extend this code, I'd recommend handling Sets, Maps, and Dates.You might want to consider using the
<details>
element instead of a custom foldable structure.In general, I avoid allowing multiple types of parameters. Instead of taking a string or an element for
showObject
's parent, I'd only take an element and change the default argument todocument.body
.Enhancement: Getters should not be evaluated until explicitly requested. This lets the user avoid evaluating getters which mutate the state (yes, unfortunately some people do this...). You can check if a key is a getter with
Reflect.getOwnPropertyDescriptor()
Demo (open inner, obj, inner):
let obj =
inner:
i: 0,
get x() return obj.inner.i++
;
obj.obj = obj;
showObject(obj);body>.levelposition:relative;left:25%;width:50%.levelbackground:lightgrey;border:2px solid brown;padding:4px;margin:4px;overflow:hidden.level>spancolor:#0366d5.level>.parentcolor:green;display:inline-block;width:100%.foldheight:18px
<script>const createElement=htmlString=>const div=document.createElement('div');div.innerHTML=htmlString;return div.firstChild;const appendElement=(parent,element)=>element=element instanceof HTMLElement?element:createElement(element);return(parent instanceof HTMLElement?parent:document.querySelector(parent)).appendChild(element);const showObject=(object,parent='body')=>Object.entries(object).forEach(([key,value])=>if(value&&value.constructor==Object)const element=appendElement(parent,`<div class="level fold"><div class="parent"><span>-</span> $key<div></div>`);element.addEventListener('click',()=>showObject(value,element),once:!0)elseappendElement(parent,`<div class="level">$key: <span>$value</span></div>`));const fold=element=>element.classList.toggle('fold');document.addEventListener('click',function()const target=event.target;const isFoldable=target.classList.contains('parent');if(isFoldable)fold(target.parentElement))</script>
Since this seemed very short, I decided to provide a simple example of one way to fix the HTML injection. I'll admit - in this case templates might be slightly overkill, but when working with even slightly more complex HTML structures, templates can be a lifesaver.
const showObject = (object, parent = document.body) =>
const keyValueTemplate = document.getElementById('keyValue');
const folderTemplate = document.getElementById('folder');
Object.entries(object).forEach(([key, value]) =>
if (value && value.constructor == Object)
// Since this structure is really simple, just create the elements.
const element = document.createElement('details');
const summary = element.appendChild(document.createElement('summary'));
summary.textContent = key;
element.addEventListener('toggle', () =>
showObject(value, element)
, once: true );
parent.appendChild(element);
else
// Use a template since the structure is somewhat complex.
const element = document.importNode(keyValueTemplate.content, true);
element.querySelector('.key').textContent = key;
element.querySelector('.value').textContent = value;
parent.appendChild(element);
);
;
showObject( a: "<b>Hi</b>", b: c: 123, d: true );
.property, details
background: lightgrey;
border: 2px solid brown;
padding: 4px;
margin: 4px;
overflow: hidden;
.value
color: #0366d5;
summary
color: green;
<template id="keyValue">
<div class="property">
<span class="key"></span>:
<span class="value"></span>
</div>
</template>
Very neat project! Your code already looks pretty clean to me. I especially like the optimization of only running showObject
once for each expansion.
Bug: If my object is
a: '<b>Bold</b>'
, the text displayed for propertya
will beBold
, not<b>Bold</b>
. This is a great example of why building HTML with strings is a bad idea. The<template>
element can be used to define an HTML structure and replicate it multiple times.Missing implementation: The Chrome devtools handle Sets, Maps, and other data structures in a very nice way, making it possible to peek inside them. Your implementation will just display them as strings (e.g.
[object Set]
). If you are looking to extend this code, I'd recommend handling Sets, Maps, and Dates.You might want to consider using the
<details>
element instead of a custom foldable structure.In general, I avoid allowing multiple types of parameters. Instead of taking a string or an element for
showObject
's parent, I'd only take an element and change the default argument todocument.body
.Enhancement: Getters should not be evaluated until explicitly requested. This lets the user avoid evaluating getters which mutate the state (yes, unfortunately some people do this...). You can check if a key is a getter with
Reflect.getOwnPropertyDescriptor()
Demo (open inner, obj, inner):
let obj =
inner:
i: 0,
get x() return obj.inner.i++
;
obj.obj = obj;
showObject(obj);body>.levelposition:relative;left:25%;width:50%.levelbackground:lightgrey;border:2px solid brown;padding:4px;margin:4px;overflow:hidden.level>spancolor:#0366d5.level>.parentcolor:green;display:inline-block;width:100%.foldheight:18px
<script>const createElement=htmlString=>const div=document.createElement('div');div.innerHTML=htmlString;return div.firstChild;const appendElement=(parent,element)=>element=element instanceof HTMLElement?element:createElement(element);return(parent instanceof HTMLElement?parent:document.querySelector(parent)).appendChild(element);const showObject=(object,parent='body')=>Object.entries(object).forEach(([key,value])=>if(value&&value.constructor==Object)const element=appendElement(parent,`<div class="level fold"><div class="parent"><span>-</span> $key<div></div>`);element.addEventListener('click',()=>showObject(value,element),once:!0)elseappendElement(parent,`<div class="level">$key: <span>$value</span></div>`));const fold=element=>element.classList.toggle('fold');document.addEventListener('click',function()const target=event.target;const isFoldable=target.classList.contains('parent');if(isFoldable)fold(target.parentElement))</script>
Since this seemed very short, I decided to provide a simple example of one way to fix the HTML injection. I'll admit - in this case templates might be slightly overkill, but when working with even slightly more complex HTML structures, templates can be a lifesaver.
const showObject = (object, parent = document.body) =>
const keyValueTemplate = document.getElementById('keyValue');
const folderTemplate = document.getElementById('folder');
Object.entries(object).forEach(([key, value]) =>
if (value && value.constructor == Object)
// Since this structure is really simple, just create the elements.
const element = document.createElement('details');
const summary = element.appendChild(document.createElement('summary'));
summary.textContent = key;
element.addEventListener('toggle', () =>
showObject(value, element)
, once: true );
parent.appendChild(element);
else
// Use a template since the structure is somewhat complex.
const element = document.importNode(keyValueTemplate.content, true);
element.querySelector('.key').textContent = key;
element.querySelector('.value').textContent = value;
parent.appendChild(element);
);
;
showObject( a: "<b>Hi</b>", b: c: 123, d: true );
.property, details
background: lightgrey;
border: 2px solid brown;
padding: 4px;
margin: 4px;
overflow: hidden;
.value
color: #0366d5;
summary
color: green;
<template id="keyValue">
<div class="property">
<span class="key"></span>:
<span class="value"></span>
</div>
</template>
let obj =
inner:
i: 0,
get x() return obj.inner.i++
;
obj.obj = obj;
showObject(obj);
body>.levelposition:relative;left:25%;width:50%.levelbackground:lightgrey;border:2px solid brown;padding:4px;margin:4px;overflow:hidden.level>spancolor:#0366d5.level>.parentcolor:green;display:inline-block;width:100%.foldheight:18px
<script>const createElement=htmlString=>const div=document.createElement('div');div.innerHTML=htmlString;return div.firstChild;const appendElement=(parent,element)=>element=element instanceof HTMLElement?element:createElement(element);return(parent instanceof HTMLElement?parent:document.querySelector(parent)).appendChild(element);const showObject=(object,parent='body')=>Object.entries(object).forEach(([key,value])=>if(value&&value.constructor==Object)const element=appendElement(parent,`<div class="level fold"><div class="parent"><span>-</span> $key<div></div>`);element.addEventListener('click',()=>showObject(value,element),once:!0)elseappendElement(parent,`<div class="level">$key: <span>$value</span></div>`));const fold=element=>element.classList.toggle('fold');document.addEventListener('click',function()const target=event.target;const isFoldable=target.classList.contains('parent');if(isFoldable)fold(target.parentElement))</script>
let obj =
inner:
i: 0,
get x() return obj.inner.i++
;
obj.obj = obj;
showObject(obj);
body>.levelposition:relative;left:25%;width:50%.levelbackground:lightgrey;border:2px solid brown;padding:4px;margin:4px;overflow:hidden.level>spancolor:#0366d5.level>.parentcolor:green;display:inline-block;width:100%.foldheight:18px
<script>const createElement=htmlString=>const div=document.createElement('div');div.innerHTML=htmlString;return div.firstChild;const appendElement=(parent,element)=>element=element instanceof HTMLElement?element:createElement(element);return(parent instanceof HTMLElement?parent:document.querySelector(parent)).appendChild(element);const showObject=(object,parent='body')=>Object.entries(object).forEach(([key,value])=>if(value&&value.constructor==Object)const element=appendElement(parent,`<div class="level fold"><div class="parent"><span>-</span> $key<div></div>`);element.addEventListener('click',()=>showObject(value,element),once:!0)elseappendElement(parent,`<div class="level">$key: <span>$value</span></div>`));const fold=element=>element.classList.toggle('fold');document.addEventListener('click',function()const target=event.target;const isFoldable=target.classList.contains('parent');if(isFoldable)fold(target.parentElement))</script>
const showObject = (object, parent = document.body) =>
const keyValueTemplate = document.getElementById('keyValue');
const folderTemplate = document.getElementById('folder');
Object.entries(object).forEach(([key, value]) =>
if (value && value.constructor == Object)
// Since this structure is really simple, just create the elements.
const element = document.createElement('details');
const summary = element.appendChild(document.createElement('summary'));
summary.textContent = key;
element.addEventListener('toggle', () =>
showObject(value, element)
, once: true );
parent.appendChild(element);
else
// Use a template since the structure is somewhat complex.
const element = document.importNode(keyValueTemplate.content, true);
element.querySelector('.key').textContent = key;
element.querySelector('.value').textContent = value;
parent.appendChild(element);
);
;
showObject( a: "<b>Hi</b>", b: c: 123, d: true );
.property, details
background: lightgrey;
border: 2px solid brown;
padding: 4px;
margin: 4px;
overflow: hidden;
.value
color: #0366d5;
summary
color: green;
<template id="keyValue">
<div class="property">
<span class="key"></span>:
<span class="value"></span>
</div>
</template>
const showObject = (object, parent = document.body) =>
const keyValueTemplate = document.getElementById('keyValue');
const folderTemplate = document.getElementById('folder');
Object.entries(object).forEach(([key, value]) =>
if (value && value.constructor == Object)
// Since this structure is really simple, just create the elements.
const element = document.createElement('details');
const summary = element.appendChild(document.createElement('summary'));
summary.textContent = key;
element.addEventListener('toggle', () =>
showObject(value, element)
, once: true );
parent.appendChild(element);
else
// Use a template since the structure is somewhat complex.
const element = document.importNode(keyValueTemplate.content, true);
element.querySelector('.key').textContent = key;
element.querySelector('.value').textContent = value;
parent.appendChild(element);
);
;
showObject( a: "<b>Hi</b>", b: c: 123, d: true );
.property, details
background: lightgrey;
border: 2px solid brown;
padding: 4px;
margin: 4px;
overflow: hidden;
.value
color: #0366d5;
summary
color: green;
<template id="keyValue">
<div class="property">
<span class="key"></span>:
<span class="value"></span>
</div>
</template>
answered Jun 24 at 21:26
Gerrit0
2,6401518
2,6401518
Hello @Guerrit0 and thank you very much for reviewing my project. I've learnt a lot reading your answer. There are a lot of things I have improved: I have actually published a follow up question just now with my new version. I haven't implemented parsing ofSet
,Map
andDate
objects nor the verification of getters, but it will come... Thanks again. Ivan
â Ivan
Jul 2 at 22:11
add a comment |Â
Hello @Guerrit0 and thank you very much for reviewing my project. I've learnt a lot reading your answer. There are a lot of things I have improved: I have actually published a follow up question just now with my new version. I haven't implemented parsing ofSet
,Map
andDate
objects nor the verification of getters, but it will come... Thanks again. Ivan
â Ivan
Jul 2 at 22:11
Hello @Guerrit0 and thank you very much for reviewing my project. I've learnt a lot reading your answer. There are a lot of things I have improved: I have actually published a follow up question just now with my new version. I haven't implemented parsing of
Set
, Map
and Date
objects nor the verification of getters, but it will come... Thanks again. Ivanâ Ivan
Jul 2 at 22:11
Hello @Guerrit0 and thank you very much for reviewing my project. I've learnt a lot reading your answer. There are a lot of things I have improved: I have actually published a follow up question just now with my new version. I haven't implemented parsing of
Set
, Map
and Date
objects nor the verification of getters, but it will come... Thanks again. Ivanâ Ivan
Jul 2 at 22:11
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f197135%2fdisplaying-javascript-objects-structure-on-page-with-html%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password