3
\$\begingroup\$

This is a follow-up post of my previous question: Displaying JavaScript object's structure on page with HTML which looked at how to display all properties of a JavaScript object on a page similar to how the console behaves in browsers. In this first question, I had figured out how to render object that had cycles.

Receiving valuable advice from Gerrit0, I changed a couple of things:

  • used template to prepare the HTML elements with JavaScript this allows to safely parse string as strings, not HTML (using textContent instead of innerHTML)

  • used the details element to make the foldable containers

  • added some CSS styling

Here's how it looks like:

enter image description here


HTML templates

<template id="keyValue">
  <div class="csle-property">
    <span class="csle-key"></span>:
    <span class="csle-value"></span>
  </div>
</template>

<template id="folder">
  <summary>
    <div>
      <div class="csle-key"></div>: 
      <div class="csle-preview"></div>
    </div>
  </summary>
</template>

JavaScript code:

/**
  * Shows all the object's properties with a depth of 1 
  * @params {Object} object, its first level properties are shown
  *         {HTMLElement} parent the element in which are displayed the properties
  * @return {undefined}
  */
const showObject = (object, parent = document.body) => {

  const template = {
    keyValue: document.querySelector('template#keyValue'),
    folder: document.querySelector('template#folder')
  }

  Object.entries(object).forEach(([key, value]) => {

    const objectClass = (value && value.constructor) ? value.constructor : undefined;
    const isContainer = (objectClass == Object) || (objectClass == Array);

    if (isContainer) { // !better naming for (objects, arrays, maps, sets)?


      const element = document.importNode(template.folder.content, true);
      const children = {
        key: element.querySelector('.csle-key'),
        preview: element.querySelector('.csle-preview')
      }

      children.key.textContent = key;
      children.preview.classList.add('csle-' + objectClass.name);

      // show four properties maximum per preview
      Object.entries(value).some(([k, v], i) => {
        type = (v && v.constructor) ? v.constructor : undefined;

        const keyValue = document.importNode(template.keyValue.content, true);
        const keyValueElement = {
          key: keyValue.querySelector('.csle-key'),
          value: keyValue.querySelector('.csle-value')
        }

        // all types need different formatting
        switch (type) {

          case Object:
            keyValueElement.value.textContent = `{...}`;
            break;

          case Array:
            keyValueElement.value.textContent = `Array(${v.length})`;
            break

          default:
            keyValueElement.value.textContent = `${v}`;

        }

        keyValueElement.key.textContent = k;
        keyValueElement.value.setAttribute('class', 'csle-' + typeof v);
        children.preview.appendChild(keyValue);
        return i == 4;
      });

      const wrapper = document.createElement('details'); // !couldn't add event listener on <details>
      wrapper.appendChild(element)
      wrapper.addEventListener('toggle', () => {
        showObject(value, wrapper)
      }, { once: true });

      parent.appendChild(wrapper);


    } else {

      const element = document.importNode(template.keyValue.content, true);
      const children = {
        key: element.querySelector('.csle-key'),
        value: element.querySelector('.csle-value')
      }

      children.key.textContent = key;

      children.value.textContent = value; // !undefined and null won't be parsed
      children.value.setAttribute('class', 'csle-' + typeof value);

      parent.appendChild(element);
    }

  });

};

Full code:

/**
  * Shows all the object's properties with a depth of 1 
  * @params {Object} object, its first level properties are shown
  *         {HTMLElement} parent the element in which are displayed the properties
  * @return {undefined}
  */
const showObject = (object, parent = document.body) => {

  const template = {
    keyValue: document.querySelector('template#keyValue'),
    folder: document.querySelector('template#folder')
  }

  Object.entries(object).forEach(([key, value]) => {

    const objectClass = (value && value.constructor) ? value.constructor : undefined;
    const isContainer = (objectClass == Object) || (objectClass == Array);

    if (isContainer) { // !better naming for (objects, arrays, maps, sets)?


      const element = document.importNode(template.folder.content, true);
      const children = {
        key: element.querySelector('.csle-key'),
        preview: element.querySelector('.csle-preview')
      }

      children.key.textContent = key;
      children.preview.classList.add('csle-' + objectClass.name);

      // show four properties maximum per preview
      Object.entries(value).some(([k, v], i) => {
        type = (v && v.constructor) ? v.constructor : undefined;

        const keyValue = document.importNode(template.keyValue.content, true);
        const keyValueElement = {
          key: keyValue.querySelector('.csle-key'),
          value: keyValue.querySelector('.csle-value')
        }

        switch (type) {

          case Object:
            keyValueElement.value.textContent = `{...}`;
            break;

          case Array:
            keyValueElement.value.textContent = `Array(${v.length})`;
            break

          default:
            keyValueElement.value.textContent = `${v}`;

        }

        keyValueElement.key.textContent = k;
        keyValueElement.value.setAttribute('class', 'csle-' + typeof v);
        children.preview.appendChild(keyValue);
        return i == 4;
      });

      const wrapper = document.createElement('details'); // !couldn't add event listener on <details>
      wrapper.appendChild(element)
      wrapper.addEventListener('toggle', () => {
        showObject(value, wrapper)
      }, {
        once: true
      });

      parent.appendChild(wrapper);


    } else {

      const element = document.importNode(template.keyValue.content, true);
      const children = {
        key: element.querySelector('.csle-key'),
        value: element.querySelector('.csle-value')
      }

      children.key.textContent = key;

      children.value.textContent = value; // !undefined and null won't be parsed
      children.value.setAttribute('class', 'csle-' + typeof value);

      parent.appendChild(element);
    }

  });

};


/**
  * For demonstration purposes
  */
const obj = {
  integer: 12,
  string: 'Hello World',
  boolean: true,
  htmlString: '<b>Nope, not bold</b>',
  object: {
    innerArray: ['first', 'second'],
    innerString: 'yes',
    innerInteger: 12,
    innerObject: {
      first: 1,
      second: 2
    }
  },
  array: ['<b>bold text</b>', 'two'],
  function: function() {}
};

obj['cycle'] = obj;
showObject(obj, document.querySelector('.container'))
/**
  *  // all properties for the console get the prefix "csle-"
  */


/**
  * Console frame
  */

.container {
    background: #242424;
    padding: 8px 15px;
}


/**
  * Property value's styling 
  * csle-{boolean, undefined, string, Array, Object}
  */
  
.csle-boolean,
.csle-undefined {
    color: #9283d1;
}
.csle-string {
    color: #ca3b38; /*#e93f3b;*/
}

.csle-string:before,
.csle-string:after { content: '"'; }

.csle-Array:before { content: '['; }
.csle-Array:after { content: ']'; }

.csle-Object:before { content: '{'; }
.csle-Object:after { content: '}'; }


/**
  * <summary> styling
  */

summary {
  margin-left: 18px;
  padding-left: 0px;
  font-size: 20px;
  display: flex;
  align-items: center;
  margin-bottom: 2px;
}
summary::-webkit-details-marker {
  color: #8d8d8d;
  text-shadow: 1em 0.1em #242424;
}

/**
  * properties styling
  */
  
details > *:not(:first-child) {
  margin-left: 22px;
}

summary > div {
  display: inline-block;
  width: 100%;
}
.csle-property, summary {
  font-family: "Lucida Grande", sans-serif;
  font-size: 17px;
  padding: 1px;
  overflow: hidden;
  padding-left: 17px;
  color: #acabab;
}
.csle-property {
  line-height: 23px;
  margin-bottom: 2px;
  font-size: 18px;
}
.csle-property > span {
  font-size: 17px;
}
.csle-preview > .csle-property {
  display: inline;
  margin: 0px;
  padding: 0px;
  margin: -2px;
  color: #acabab;
  font-size: 18px;
}

.csle-preview > .csle-property > span {
  font-size: 17px;
}

.csle-preview > .csle-property:not(:first-child) > .csle-key:before  {
  content: ', ';
}


.csle-key, summary {
  color: #a545ac;
  font-size: 17px
}
.csle-key, .csle-preview {
  display: inline;
}
<div class="container"></div>

<template id="keyValue">
  <div class="csle-property">
    <span class="csle-key"></span>:
    <span class="csle-value"></span>
  </div>
</template>

<template id="folder">
  <summary>
    <div>
      <div class="csle-key"></div>: 
      <div class="csle-preview"></div>
    </div>
  </summary>
</template>


Issues:

  • my function showObject seems to big.

  • Unsure if I'm using ECMAScript 6 properly

  • some of the variable names bother me (element, childrenPreview, isContainer) as they aren't very clear.

  • I can't attach an event listener on the imported template <details> because it is a DocumentFragment. To fix that I had to (line 62 of the snippet):

    1. only import <summary> as a template
    2. create the <details> element manually
    3. append <summary> inside <details>
    4. attach the event listener to <details>


    Any workaround?

I'm looking forward to reading your comments.

\$\endgroup\$
  • \$\begingroup\$ Very nice! I see a few small things, but I'll hold off on reviewing as I believe it would be more valuable for you to receive a review from someone with a slightly different perspective. \$\endgroup\$ – Gerrit0 Jul 3 '18 at 11:29
  • 2
    \$\begingroup\$ There is an undefined variable type first line in the inner loop some You should always add the "use strict" directive as that type of bug is very bad. \$\endgroup\$ – Blindman67 Jul 3 '18 at 12:35
  • \$\begingroup\$ Your use of ES6 features seems fine. Small thing: I feel that when used as a padding after { and before }, line feeds just take up vertical space. If you do choose to use them, you should probably pad the if and else equally. \$\endgroup\$ – shreyasminocha Jul 10 '18 at 19:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.