<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossOrigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css"
integrity="sha512-1ycn6IcaQQ40/MKBW2W4Rhis/DbILU74C1vSrLJxCq57o941Ym01SwNsOMqvEBFlcgUa6xLiPY/NS5R+E6ztJQ=="
crossOrigin="anonymous" referrerpolicy="no-referrer"/>
<script type="module">
// ? main script {Node, Tree, JsonView}
class Node {
/**
* @description Add more attributes to the item.
* @param {*} item
* @param {*} key
* @param {Node} parent
* */
constructor(item, key, parent) {
this.key = key
/** @param {string} */
this.type = Array.isArray(item) ? "array" : typeof item
/** @param {Number} */
this.depth = parent ? parent.depth + 1 : 0
this.value = item
this.parent = parent
/** @param {[Node]} */
this.children = []
}
}
class Tree {
/**
* @description Given the root node, it will complete the children of it.
* @param {Node} rootNode
*/
constructor(rootNode) {
this.root = rootNode
const obj = this.root.value
if (!(obj instanceof Object)) { // Array is an Object too.
return
}
Object.keys(obj).forEach(keyOrIdx => {
const value = obj[keyOrIdx]
const subNode = new Node(value, keyOrIdx, rootNode)
const subTree = new Tree(subNode)
rootNode.children.push(subTree.root)
})
}
/**
* @param {string | Object} jsonData
* @return {Tree}
*/
static CreateTree(jsonData) {
jsonData = typeof jsonData === "string" ? JSON.parse(jsonData) : jsonData
const rootNode = new Node(jsonData, "root", null)
return new Tree(rootNode)
}
}
class JsonView {
static DefaultColorMap = {
text: {
string: "green",
number: "#f9ae58",
boolean: "#ca4ff8",
array: "black",
object: "black",
},
bg: {
object: "none"
// ... You can add more by yourself. They are like the text as above.
}
}
static NewConfig() {
return JSON.parse(JSON.stringify(JsonView.DefaultColorMap))
}
static SEPARATOR = " : "
/** @type {Tree} */
#tree
/**
* @param {Tree} tree
* */
constructor(tree) {
this.#tree = tree
}
/**
* @param {Node} node
* @param {Object} colorMap
*/
#render(node, colorMap = JsonView.DefaultColorMap) {
/**
* @param {Node} node
* */
const getValue = (node) => {
const typeName = node.type
switch (typeName) {
case "object":
return `object {${Object.keys(node.value).length}}`
case "array":
return `array [${Object.keys(node.value).length}]`
default:
return node.value
}
}
const arrowIcon = ["object", "array"].includes(node.type) ? `<i class="fas fa-caret-down"></i>` : ""
const divFlag = document.createRange().createContextualFragment(`<div style="margin-left:${node.depth * 18}px">${arrowIcon}</div>`)
const divElem = divFlag.querySelector("div")
const textColor = colorMap.text[node.type] !== undefined ? `color:${colorMap.text[node.type]}` : ""
const bgColor = colorMap.bg[node.type] !== undefined ? `background-color:${colorMap.bg[node.type]}` : ""
const valueStyle = (textColor + bgColor).length > 0 ? `style=${[textColor, bgColor].join(";")}` : ""
const keyName = node.depth !== 0 ? node.key + JsonView.SEPARATOR : "" // depth = 0 its key is "root" which is created by the system, so ignore it.
const spanFlag = document.createRange().createContextualFragment(
`<span class="ms-2">${keyName}<span ${valueStyle}>${getValue(node)}</span></span>`
)
const isCollapsible = ["object", "array"].includes(node.type)
node.children.forEach(subNode => {
const subElem = this.#render(subNode, colorMap)
if (isCollapsible) {
divFlag.querySelector(`i`).addEventListener("click", (e) => {
e.stopPropagation()
subElem.dataset.toggle = subElem.dataset.toggle === undefined ? "none" :
subElem.dataset.toggle === "none" ? "" : "none"
e.target.className = subElem.dataset.toggle === "none" ? "fas fa-caret-right" : "fas fa-caret-down" // Change the icon to → or ↓
subElem.querySelectorAll(`*`).forEach(e => e.style.display = subElem.dataset.toggle)
})
}
spanFlag.append(subElem)
})
divElem.append(spanFlag)
return divElem
}
/**
* @param {Element} targetElem
* @param {?Object} colorMap
*/
render(targetElem, colorMap = JsonView.DefaultColorMap) {
targetElem.append(this.#render(this.#tree.root, colorMap))
}
}
// ? Below is Test
function main(outputElem) {
const testObj = {
db: {
port: 1234,
name: "My db",
tables: [
{id: 1, name: "table 1"},
{id: 2, name: "table 2"},
],
},
options: {
debug: false,
ui: true,
},
person: [
"Foo",
"Bar"
]
}
const tree = Tree.CreateTree(testObj)
const jsonView = new JsonView(tree)
jsonView.render(outputElem)
/* If you want to set the color by yourself, you can try as below
const config = JsonView.NewConfig()
config.bg.object = "red"
jsonView.render(outputElem, config)
*/
}
(() => {
window.onload = () => {
main(document.body)
}
})()
</script>