Displaying JavaScript object's structure on page with HTML

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
5
down vote

favorite
1












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:



enter image description here



So I'm searching to:



  1. display all of the object's properties (even if they're nested)


  2. 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?






share|improve this question



























    up vote
    5
    down vote

    favorite
    1












    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:



    enter image description here



    So I'm searching to:



    1. display all of the object's properties (even if they're nested)


    2. 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?






    share|improve this question























      up vote
      5
      down vote

      favorite
      1









      up vote
      5
      down vote

      favorite
      1






      1





      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:



      enter image description here



      So I'm searching to:



      1. display all of the object's properties (even if they're nested)


      2. 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?






      share|improve this question













      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:



      enter image description here



      So I'm searching to:



      1. display all of the object's properties (even if they're nested)


      2. 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>








      share|improve this question












      share|improve this question




      share|improve this question








      edited Jul 2 at 22:13
























      asked Jun 23 at 20:29









      Ivan

      33511




      33511




















          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.



          1. Bug: If my object is a: '<b>Bold</b>' , the text displayed for property a will be Bold, 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.


          2. 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.


          3. You might want to consider using the <details> element instead of a custom foldable structure.


          4. 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 to document.body.



          5. 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>








          share|improve this answer





















          • 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










          Your Answer




          StackExchange.ifUsing("editor", function ()
          return StackExchange.using("mathjaxEditing", function ()
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          );
          );
          , "mathjax-editing");

          StackExchange.ifUsing("editor", function ()
          StackExchange.using("externalEditor", function ()
          StackExchange.using("snippets", function ()
          StackExchange.snippets.init();
          );
          );
          , "code-snippets");

          StackExchange.ready(function()
          var channelOptions =
          tags: "".split(" "),
          id: "196"
          ;
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function()
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled)
          StackExchange.using("snippets", function()
          createEditor();
          );

          else
          createEditor();

          );

          function createEditor()
          StackExchange.prepareEditor(
          heartbeatType: 'answer',
          convertImagesToLinks: false,
          noModals: false,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          );



          );








           

          draft saved


          draft discarded


















          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






























          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.



          1. Bug: If my object is a: '<b>Bold</b>' , the text displayed for property a will be Bold, 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.


          2. 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.


          3. You might want to consider using the <details> element instead of a custom foldable structure.


          4. 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 to document.body.



          5. 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>








          share|improve this answer





















          • 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














          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.



          1. Bug: If my object is a: '<b>Bold</b>' , the text displayed for property a will be Bold, 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.


          2. 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.


          3. You might want to consider using the <details> element instead of a custom foldable structure.


          4. 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 to document.body.



          5. 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>








          share|improve this answer





















          • 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












          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.



          1. Bug: If my object is a: '<b>Bold</b>' , the text displayed for property a will be Bold, 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.


          2. 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.


          3. You might want to consider using the <details> element instead of a custom foldable structure.


          4. 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 to document.body.



          5. 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>








          share|improve this answer













          Very neat project! Your code already looks pretty clean to me. I especially like the optimization of only running showObject once for each expansion.



          1. Bug: If my object is a: '<b>Bold</b>' , the text displayed for property a will be Bold, 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.


          2. 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.


          3. You might want to consider using the <details> element instead of a custom foldable structure.


          4. 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 to document.body.



          5. 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>






          share|improve this answer













          share|improve this answer



          share|improve this answer











          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 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















          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












           

          draft saved


          draft discarded


























           


          draft saved


          draft discarded














          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













































































          Popular posts from this blog

          Greedy Best First Search implementation in Rust

          Function to Return a JSON Like Objects Using VBA Collections and Arrays

          C++11 CLH Lock Implementation