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\$
3
  • \$\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
    Commented Jul 3, 2018 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
    Commented Jul 3, 2018 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\$ Commented Jul 10, 2018 at 19:08

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.