Skip to content

Commit da9509f

Browse files
committed
First version
1 parent 406dbd0 commit da9509f

File tree

4 files changed

+228
-1
lines changed

4 files changed

+228
-1
lines changed

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,10 @@
1-
# json-element
1+
[![edit-in-webcomponents.dev](https://webcomponents.dev/assets/ext/edit_in_wcd.svg)](https://webcomponents.dev/edit/VXr2i1M8U1s4He9bOdII)
2+
# `<json-element>`
3+
4+
# Credits
5+
6+
Inspired by [svelte-json-tree](https://github.com/tanhauhau/svelte-json-tree)
7+
8+
Challenged by [@passle\_](https://twitter.com/passle_) "Can you do it with 0 dependencies?!" 💪
9+
10+
> Created with [webcomponents.dev](https://webcomponents.dev)

src/index.js

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
export default class JSONElement extends HTMLElement {
2+
constructor() {
3+
super();
4+
const shadow = this.attachShadow({ mode: "open" });
5+
this.value = {};
6+
}
7+
8+
static get styles() {
9+
return `
10+
:host {
11+
font-family: monospace;
12+
}
13+
14+
details > div {
15+
padding-left: 15px;
16+
}
17+
18+
.key {
19+
color: purple;
20+
}
21+
22+
.value {
23+
color: green;
24+
}
25+
26+
.less {
27+
color: grey;
28+
}
29+
30+
`;
31+
}
32+
33+
connectedCallback() {
34+
this.render();
35+
}
36+
37+
render() {
38+
this.shadowRoot.innerHTML = `
39+
<style>
40+
${JSONElement.styles}
41+
</style>
42+
${this.renderNode(undefined, this.value)}`;
43+
}
44+
45+
//
46+
// Main Renders
47+
//
48+
49+
renderNode(key, obj) {
50+
const type = JSONElement.objType(obj);
51+
switch (type) {
52+
case "Object":
53+
case "Array":
54+
return this.renderParent(type, key, obj);
55+
default:
56+
return this.renderKeyValue(key, this.renderValue(type, obj));
57+
}
58+
}
59+
60+
renderValue(type, value) {
61+
switch (type) {
62+
case "Boolean":
63+
return `${value ? "true" : "false"}`;
64+
case "String":
65+
return `"${value}"`;
66+
case "Number":
67+
return `${value}`;
68+
case "Date":
69+
return `${value.toISOString()}`;
70+
case "Null":
71+
return "null";
72+
case "Undefined":
73+
return "undefined";
74+
case "Function":
75+
case "Symbol":
76+
return `${value.toString()}`;
77+
default:
78+
return `###unsupported yet###`;
79+
}
80+
}
81+
82+
renderParent(type, key, value) {
83+
const summary = `<summary>${this.renderSummaryObject(
84+
type,
85+
key,
86+
value
87+
)}</summary>`;
88+
89+
let details = "";
90+
const keys = Reflect.ownKeys(value);
91+
keys.forEach(key => {
92+
details += this.renderNode(key, value[key]);
93+
});
94+
95+
return `<details>${summary}<div>${details}</div></details>`;
96+
}
97+
98+
renderKeyValue(key, value) {
99+
return `<div>${this.renderSpanKey(key)}${this.renderSpanValue(
100+
value
101+
)}</div>`;
102+
}
103+
104+
renderSpanKey(key) {
105+
return key ? `<span class="key">${key}: </span>` : "";
106+
}
107+
108+
renderSpanValue(value) {
109+
return value ? `<span class="value">${value}</span>` : "";
110+
}
111+
112+
renderSpanLessImportant(value) {
113+
return value ? `<span class="less">${value}</span>` : "";
114+
}
115+
116+
//
117+
// Summary renders
118+
//
119+
120+
renderSummaryObject(type, key, value) {
121+
const frontkey = this.renderSpanKey(key);
122+
123+
let openSummary = "";
124+
let closeSummary = "";
125+
126+
switch (type) {
127+
case "Object":
128+
openSummary = "Object: {";
129+
closeSummary = "}";
130+
break;
131+
case "Array":
132+
openSummary = "Array: [";
133+
closeSummary = "]";
134+
break;
135+
}
136+
137+
const keys = Reflect.ownKeys(value);
138+
139+
const content = keys.reduce((accu, key, index) => {
140+
if (index > 5) return accu;
141+
if (index == 5) return accu + ` ${this.renderSpanLessImportant("...")}`;
142+
// less than 5 items show in summary
143+
const child = value[key];
144+
return (
145+
accu + ` ${this.renderSpanKey(key)}${this.renderSummaryValue(child)}`
146+
);
147+
}, "");
148+
149+
return `${frontkey}${openSummary} ${content} ${closeSummary}`;
150+
}
151+
152+
renderSummaryValue(value) {
153+
const type = JSONElement.objType(value);
154+
switch (type) {
155+
case "Object":
156+
return this.renderSpanLessImportant("{...}");
157+
case "Array":
158+
return this.renderSpanLessImportant("[...]");
159+
default:
160+
return this.renderSpanValue(this.renderValue(type, value));
161+
}
162+
}
163+
164+
//
165+
// Tools
166+
//
167+
168+
static objType(obj) {
169+
const type = Object.prototype.toString.call(obj).slice(8, -1);
170+
if (type === "Object") {
171+
if (typeof obj[Symbol.iterator] === "function") {
172+
return "Iterable";
173+
}
174+
return obj.constructor.name;
175+
}
176+
177+
return type;
178+
}
179+
}

src/index.spec.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// BDD-Style Testing (powered by https://mochajs.org/)
2+
//
3+
// Import your stories
4+
// import * as stories from "./index.stories.js";
5+
//
6+
// Use any renderer for you stories
7+
// import { fixture } from "@open-wc/testing-helpers";
8+
//
9+
// Use any assert library
10+
import chai from "chai/chai.js";
11+
const expect = chai.expect;
12+
13+
describe("Dummy test", function() {
14+
it("should be always true", function() {
15+
expect(true).to.be.true;
16+
});
17+
});

src/index.stories.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { html } from "lit-html";
2+
3+
const value = {
4+
array: [1, 2, 3],
5+
bool: true,
6+
date: new Date(),
7+
object: {
8+
foo: "bar"
9+
},
10+
symbol: Symbol("foo"),
11+
nested: [
12+
{
13+
a: [1, "2", null, undefined]
14+
}
15+
]
16+
};
17+
18+
console.log(value);
19+
20+
export const story1 = () => html`
21+
<custom-element .value=${value}> </custom-element>
22+
`;

0 commit comments

Comments
 (0)