(function() { 'use strict'; /** * Create html element * @param {String} type html element * @param {Object} config */ function createElement(type, config) { const htmlElement = document.createElement(type); if (config === undefined) { return htmlElement; } if (config.className) { htmlElement.className = config.className; } if (config.content) { htmlElement.textContent = config.content; } if (config.children) { config.children.forEach((el) => { if (el !== null) { htmlElement.appendChild(el); } }); } return htmlElement; } /** * @param {Object} node * @return {HTMLElement} */ function createExpandedElement(node) { const iElem = createElement('i'); if (node.expanded) { iElem.className = 'fas fa-caret-down'; } else { iElem.className = 'fas fa-caret-right'; } const caretElem = createElement('div', { className: 'caret-icon', children: [iElem], }); const handleClick = node.toggle.bind(node); caretElem.addEventListener('click', handleClick); const indexElem = createElement('div', { className: 'json-index', content: node.key, }); const typeElem = createElement('div', { className: 'json-type', content: node.type, }); const keyElem = createElement('div', { className: 'json-key', content: node.key, }); const sizeElem = createElement('div', { className: 'json-size' }); if (node.type === 'array') { sizeElem.innerText = '[' + node.children.length + ']'; } else if (node.type === 'object') { sizeElem.innerText = '{' + node.children.length + '}'; } let lineChildren; if (node.key === null) { lineChildren = [caretElem, typeElem, sizeElem] } else if (node.parent.type === 'array') { lineChildren = [caretElem, indexElem, sizeElem] } else { lineChildren = [caretElem, keyElem, sizeElem] } const lineElem = createElement('div', { className: 'line', children: lineChildren }); if (node.depth > 0) { lineElem.style = 'margin-left: ' + node.depth * 24 + 'px;'; } return lineElem; } /** * @param {Object} node * @return {HTMLElement} */ function createNotExpandedElement(node) { const caretElem = createElement('div', { className: 'empty-icon', }); const keyElem = createElement('div', { className: 'json-key', content: node.key }); const separatorElement = createElement('div', { className: 'json-separator', content: ':' }); const valueType = ' json-' + typeof node.value; const valueContent = String(node.value); const valueElement = createElement('div', { className: 'json-value' + valueType, content: valueContent }); const lineElem = createElement('div', { className: 'line', children: [caretElem, keyElem, separatorElement, valueElement] }); if (node.depth > 0) { lineElem.style = 'margin-left: ' + node.depth * 24 + 'px;'; } return lineElem; } /** * create tree node * @return {Object} */ function createNode() { return { key: null, parent: null, value: null, expanded: false, type: null, children: null, elem: null, depth: 0, setCaretIconRight() { const icon = this.elem.querySelector('.fas'); icon.classList.replace('fa-caret-down', 'fa-caret-right'); }, setCaretIconDown() { const icon = this.elem.querySelector('.fas'); icon.classList.replace('fa-caret-right', 'fa-caret-down'); }, hideChildren() { if (this.children !== null) { this.children.forEach((item) => { item.elem.classList.add('hide'); if (item.expanded) { item.hideChildren(); } }); } }, showChildren() { if (this.children !== null) { this.children.forEach((item) => { item.elem.classList.remove('hide'); if (item.expanded) { item.showChildren(); } }); } }, toggle: function() { if (this.expanded) { this.expanded = false; this.hideChildren(); this.setCaretIconRight(); } else { this.expanded = true; this.showChildren(); this.setCaretIconDown(); } } } } /** * Return object length * @param {Object} obj * @return {number} */ function getLength(obj) { let length = 0; for (let key in obj) { length += 1; }; return length; } /** * Return variable type * @param {*} val */ function getType(val) { let type = typeof val; if (Array.isArray(val)) { type = 'array'; } else if (val === null) { type = 'null'; } return type; } /** * Recursively traverse json object * @param {Object} obj parsed json object * @param {Object} parent of object tree */ function traverseObject(obj, parent) { for (let key in obj) { const child = createNode(); child.parent = parent; child.key = key; child.type = getType(obj[key]); child.depth = parent.depth + 1; child.expanded = false; if (typeof obj[key] === 'object') { child.children = []; parent.children.push(child); traverseObject(obj[key], child); child.elem = createExpandedElement(child); } else { child.value = obj[key]; child.elem = createNotExpandedElement(child); parent.children.push(child); } } } /** * Create root of a tree * @param {Object} obj Json object * @return {Object} */ function createTree(obj) { const tree = createNode(); tree.type = getType(obj); tree.children = []; tree.expanded = true; traverseObject(obj, tree); tree.elem = createExpandedElement(tree); return tree; } /** * Recursively traverse Tree object * @param {Object} node * @param {Callback} callback */ function traverseTree(node, callback) { callback(node); if (node.children !== null) { node.children.forEach((item) => { traverseTree(item, callback); }); } } /** * Render Tree object * @param {Object} tree * @param {String} targetElem */ function render(tree, targetElem) { let rootElem; if (targetElem) { rootElem = document.querySelector(targetElem); } else { rootElem = document.body; } traverseTree(tree, (node) => { if (!node.expanded) { node.hideChildren(); } rootElem.appendChild(node.elem); }); } /* Export jsonView object */ window.jsonView = { /** * Render JSON into DOM container * @param {String} jsonData * @param {String} targetElem */ format: function(jsonData, targetElem) { let parsedData = jsonData; if (typeof jsonData === 'string' || jsonData instanceof String) parsedData = JSON.parse(jsonData); const tree = createTree(parsedData); render(tree, targetElem); } } })();