Skip to content

Commit 0163d10

Browse files
committed
Add support for displaying basic JavaScript objects.
1 parent 111f641 commit 0163d10

16 files changed

+882
-241
lines changed

README.md

+16
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ Example run with `parcel` (`npm install -D parce-bundler`);
5151

5252
parcel index.html
5353

54+
## More examples
55+
56+
See [examples/basic/index.html](examples/basic/index.html) for plain javascript example.
57+
58+
See [docs-src/demo.ts](docs-src/demo.ts) for a more advanced example.
59+
5460
## Getting started
5561

5662
You can use the following static method to get a new viewer instance:
@@ -78,6 +84,16 @@ Example:
7884

7985
## API
8086

87+
## `BigJsonViewerDom` static methods
88+
89+
#### `fromData(data: ArrayBuffer | string, options?: BigJsonViewerOptions): Promise<BigJsonViewerDom>`
90+
91+
Initilizes a new viewer with JSON encoded data
92+
93+
#### `fromObject(data: string | object | null | number | boolean, options?: BigJsonViewerOptions): Promise<BigJsonViewerDom>`
94+
95+
Initializes a new viewer with JavaScript data
96+
8197
## `BigJsonViewerDom` methods
8298

8399
#### `getRootElement()`

docs/demo.275eac1b.map

-1
This file was deleted.

docs/demo.275eac1b.js docs/demo.48badbe0.js

+203-199
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/demo.48badbe0.map

+1
Large diffs are not rendered by default.

docs/index.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Big JSON Viewer Demo</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous"> <link rel="stylesheet" href="default.23c40554.css"> <style>body{font-family:Helvetica,serif}</style> </head> <body> <div class="p-3" style="position:fixed;top:0;right:0;background:#fff;border:1px solid #ccc"> <input placeholder="Search" id="search"> <span id="searchInfo"></span> </div> <div class="container"> <h1 class="mt-3">Big JSON Viewer Demo</h1> <p> Watch large JSON structures in the Browser.<br> <a href="https://github.com/dhcode/big-json-viewer">On GitHub</a> </p> <h4 class="mt-3">JSON text</h4> <div> <a href="javascript:" data-load="simpleData">Simple test data</a> | <a href="javascript:" data-load="largeData">Large data</a> </div> <div> <textarea id="code" style="min-height:15em;width:100%;font-family:monospace,serif;font-size:.8em"></textarea> </div> <h4 class="mt-3">Big JSON Viewer output</h4> <div id="viewer"> </div> <h5 class="mt-3">Open paths</h5> <div> <textarea id="paths" style="min-height:6em;width:100%;font-family:monospace,serif;font-size:.8em"></textarea> </div> <h5 class="mt-3">Copied paths</h5> <div> <input id="copied" style="width:100%;font-family:monospace,serif;font-size:.8em"> </div> <footer class="mt-5"> Created by <a href="https://github.com/dhcode">Dominik Herbst</a> </footer> </div> <script src="demo.275eac1b.js"></script> </body> </html>
1+
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Big JSON Viewer Demo</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous"> <link rel="stylesheet" href="default.23c40554.css"> <style>body{font-family:Helvetica,serif}</style> </head> <body> <div class="p-3" style="position:fixed;top:0;right:0;background:#fff;border:1px solid #ccc"> <input placeholder="Search" id="search"> <span id="searchInfo"></span> </div> <div class="container"> <h1 class="mt-3">Big JSON Viewer Demo</h1> <p> Watch large JSON structures in the Browser.<br> <a href="https://github.com/dhcode/big-json-viewer">On GitHub</a> </p> <h4 class="mt-3">JSON text</h4> <div> <a href="javascript:" data-load="simpleData">Simple test data</a> | <a href="javascript:" data-load="largeData">Large data</a> </div> <div> <textarea id="code" style="min-height:15em;width:100%;font-family:monospace,serif;font-size:.8em"></textarea> </div> <h4 class="mt-3">Big JSON Viewer output</h4> <div id="viewer"> </div> <h5 class="mt-3">Open paths</h5> <div> <textarea id="paths" style="min-height:6em;width:100%;font-family:monospace,serif;font-size:.8em"></textarea> </div> <h5 class="mt-3">Copied paths</h5> <div> <input id="copied" style="width:100%;font-family:monospace,serif;font-size:.8em"> </div> <footer class="mt-5"> Created by <a href="https://github.com/dhcode">Dominik Herbst</a> </footer> </div> <script src="demo.48badbe0.js"></script> </body> </html>

examples/basic/index.html

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
<head>
44
<meta charset="UTF-8">
55
<title>JSON Viewer basic example</title>
6-
<link rel="stylesheet" href="../../dist/default.css">
7-
<script src="../../dist/browser-api.js"></script>
6+
<link rel="stylesheet" href="https://unpkg.com/big-json-viewer/dist/default.css">
7+
<script src="https://unpkg.com/big-json-viewer/dist/browser-api.js"></script>
88
</head>
99
<body>
1010
<script>

src/big-json-viewer-dom.spec.ts

+23
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@ describe('Big JSON Viewer', function() {
2323
viewer.destroy();
2424
});
2525

26+
it('should create DOM from JavaScript simple object', async function() {
27+
const viewer = await BigJsonViewerDom.fromObject({ 'a': 5, 'b': true });
28+
const root = viewer.getRootElement();
29+
expect(root).toBeTruthy();
30+
await root.openAll();
31+
expect(root.getOpenPaths()).toEqual([[]]);
32+
33+
expect(root.childrenElement).toBeTruthy();
34+
expect(root.childrenElement.children.length).toEqual(2);
35+
viewer.destroy();
36+
});
37+
2638
it('should create DOM from more complex object', async function() {
2739
const data =
2840
'{"hello": "hello world, is a great world","test": [0,"old world",{"worldgame": true}]}';
@@ -109,6 +121,11 @@ describe('Big JSON Viewer', function() {
109121
expect(await viewer.openBySearch(null)).toBeNull();
110122
expect(root.isNodeOpen()).toBeFalsy();
111123

124+
const cursor2 = await viewer.openBySearch(/old/);
125+
expect(cursor2).toBeTruthy();
126+
expect(cursor2.matches.length).toEqual(1);
127+
expect(root.getOpenPaths()).toEqual([['test']]);
128+
112129
viewer.destroy();
113130
});
114131

@@ -128,30 +145,36 @@ describe('Big JSON Viewer', function() {
128145
expect(await root.openNode()).toBeTruthy();
129146
expect(root.childrenElement).toBeTruthy();
130147
expect(root.childrenElement.children.length).toEqual(3);
148+
expect(root.getOpenPaths()).toEqual([[]]);
131149

132150
let stub = root.childrenElement.children[0] as JsonNodesStubElement;
133151
expect(stub.isNodeOpen()).toBeFalsy();
134152

135153
expect(await stub.openNode()).toBeTruthy();
136154
expect(stub.childrenElement).toBeTruthy();
137155
expect(stub.childrenElement.children.length).toEqual(50);
156+
expect(root.getOpenPaths()).toEqual([['0']]);
138157

139158
expect(await stub.closeNode()).toBeTruthy();
140159
expect(stub.childrenElement).toBeNull();
160+
expect(root.getOpenPaths()).toEqual([[]]);
141161

142162
stub = root.childrenElement.children[2] as JsonNodesStubElement;
143163
expect(stub.isNodeOpen()).toBeFalsy();
144164
stub.querySelector('a').dispatchEvent(new MouseEvent('click'));
145165
await wait(1);
146166
expect(stub.childrenElement).toBeTruthy();
147167
expect(stub.childrenElement.children.length).toEqual(20);
168+
expect(root.getOpenPaths()).toEqual([['100']]);
148169

149170
await root.closeNode();
171+
expect(root.getOpenPaths()).toEqual([]);
150172

151173
const openedNode = await root.openPath(['61']);
152174
expect(openedNode).toBeTruthy();
153175
expect(openedNode.jsonNode.type).toEqual('boolean');
154176
expect(openedNode.jsonNode.path).toEqual(['61']);
177+
expect(root.getOpenPaths()).toEqual([['50']]);
155178

156179
stub = root.childrenElement.children[0] as JsonNodesStubElement;
157180
expect(stub.isNodeOpen()).toBeFalsy();

src/big-json-viewer-dom.ts

+21
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ export class BigJsonViewerDom {
6363

6464
private rootNode: BigJsonViewerNode;
6565

66+
/**
67+
* Initialized the viewer with JSON encoded data
68+
*/
6669
public static async fromData(
6770
data: ArrayBuffer | string,
6871
options?: BigJsonViewerOptions
@@ -72,6 +75,18 @@ export class BigJsonViewerDom {
7275
return viewer;
7376
}
7477

78+
/**
79+
* Initializes the viewer with a JavaScript object
80+
*/
81+
public static async fromObject(
82+
data: string | object | null | number | boolean,
83+
options?: BigJsonViewerOptions
84+
): Promise<BigJsonViewerDom> {
85+
const viewer = new BigJsonViewerDom(options);
86+
await viewer.setObject(data);
87+
return viewer;
88+
}
89+
7590
protected constructor(options?: BigJsonViewerOptions) {
7691
if (options) {
7792
Object.assign(this.options, options);
@@ -110,6 +125,12 @@ export class BigJsonViewerDom {
110125
return this.rootNode;
111126
}
112127

128+
protected async setObject(data: any): Promise<BigJsonViewerNode> {
129+
const client = await this.getWorkerClient();
130+
this.rootNode = await client.call('initWithJs', data);
131+
return this.rootNode;
132+
}
133+
113134
protected async getChildNodes(
114135
path: string[],
115136
start: number,

src/big-json-viewer-service.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,23 @@ import {
99
BufferJsonParser
1010
} from './parser/buffer-json-parser';
1111
import { JsonNodeInfo } from './parser/json-node-info';
12+
import { JsParser } from './parser/js-parser';
1213

1314
export class BigJsonViewerService {
14-
rootNode: BufferJsonNodeInfo;
15+
rootNode: JsonNodeInfo;
1516

1617
initWithData(data: ArrayBuffer | string): BigJsonViewerNode {
1718
this.rootNode = new BufferJsonParser(data).getRootNodeInfo();
1819

1920
return this.getRenderInfo(this.rootNode);
2021
}
2122

23+
initWithJs(data: any): BigJsonViewerNode {
24+
this.rootNode = new JsParser(data).getRootNodeInfo();
25+
26+
return this.getRenderInfo(this.rootNode);
27+
}
28+
2229
getNodes(path: string[], start: number, limit: number): BigJsonViewerNode[] {
2330
const node = this.rootNode.getByPath(path);
2431
if (node && node.type === 'object') {

src/helpers/utils.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export function assertStartLimit(start, limit) {
2+
if (isNaN(start) || start < 0) {
3+
throw new Error(`Invalid start ${start}`);
4+
}
5+
if (limit && limit < 0) {
6+
throw new Error(`Invalid limit ${limit}`);
7+
}
8+
}

src/helpers/worker-client.spec.ts

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { WorkerClient, WorkerClientMock } from './worker-client';
2+
import { initProvider } from './worker-provider';
3+
4+
class MockMessageEvent {
5+
constructor(public data) {
6+
7+
}
8+
}
9+
10+
class MockErrorEvent {
11+
constructor(public message) {
12+
13+
}
14+
}
15+
16+
class MockWorker {
17+
onerror: (ev: MockErrorEvent) => any;
18+
onmessage: (ev: MockMessageEvent) => any;
19+
20+
postMessage(message: any, transfer?: any[]): void {
21+
}
22+
23+
terminate(): void {
24+
}
25+
}
26+
27+
class MockScope {
28+
onerror: (ev: MockErrorEvent) => any;
29+
onmessage: (ev: MockMessageEvent) => any;
30+
31+
constructor(private worker: MockWorker) {
32+
worker.postMessage = (message: any, transfer?: any[]) => {
33+
this.onmessage(new MockMessageEvent(message));
34+
};
35+
}
36+
37+
postMessage(message: any, transfer?: any[]): void {
38+
this.worker.onmessage(new MockMessageEvent(message));
39+
}
40+
41+
}
42+
43+
describe('Worker Client', function() {
44+
it('should init client with worker', async function() {
45+
const mockWorker = new MockWorker();
46+
const mockScope = new MockScope(mockWorker);
47+
initProvider({}, mockScope);
48+
49+
const client = new WorkerClient(mockWorker as any as Worker);
50+
expect(client).toBeTruthy();
51+
await expect(client.initWorker()).resolves.toBeTruthy();
52+
53+
spyOn(mockWorker, 'terminate');
54+
55+
client.destroy();
56+
57+
expect(mockWorker.terminate).toHaveBeenCalled();
58+
});
59+
60+
it('should fail to init', async function() {
61+
const mockWorker = new MockWorker();
62+
mockWorker.postMessage = (msg) => {
63+
mockWorker.onerror(new MockErrorEvent('failed'));
64+
};
65+
const client = new WorkerClient(mockWorker as any as Worker);
66+
expect(client).toBeTruthy();
67+
await expect(client.initWorker()).rejects.toEqual({ message: 'failed' });
68+
});
69+
70+
it('should request hello', async function() {
71+
const mockWorker = new MockWorker();
72+
const mockScope = new MockScope(mockWorker);
73+
initProvider({
74+
hello(name: string) {
75+
return 'Hello ' + name;
76+
}
77+
}, mockScope);
78+
79+
const client = new WorkerClient(mockWorker as any as Worker);
80+
expect(client).toBeTruthy();
81+
await expect(client.initWorker()).resolves.toBeTruthy();
82+
await expect(client.call('hello', 'World')).resolves.toBe('Hello World');
83+
84+
});
85+
86+
it('should fail', async function() {
87+
const mockWorker = new MockWorker();
88+
const mockScope = new MockScope(mockWorker);
89+
initProvider({
90+
fail() {
91+
throw new Error('failed');
92+
}
93+
}, mockScope);
94+
95+
const client = new WorkerClient(mockWorker as any as Worker);
96+
expect(client).toBeTruthy();
97+
await expect(client.initWorker()).resolves.toBeTruthy();
98+
await expect(client.call('fail')).rejects.toBe(new Error('failed').toString());
99+
100+
});
101+
102+
});
103+
104+
describe('Worker Client Mock', function() {
105+
it('should call hello', async function() {
106+
const client = new WorkerClientMock({
107+
hello(name) {
108+
return 'Hello ' + name;
109+
}
110+
});
111+
112+
expect(client).toBeTruthy();
113+
114+
await expect(client.call('hello', 'World')).resolves.toBe('Hello World');
115+
116+
client.destroy();
117+
118+
});
119+
});

src/helpers/worker-provider.ts

+1-13
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,4 @@
1-
import { BufferJsonParser } from '../parser/buffer-json-parser';
2-
3-
declare interface DedicatedWorkerGlobalScope {
4-
onmessage: (this: DedicatedWorkerGlobalScope, ev: MessageEvent) => any;
5-
6-
close(): void;
7-
8-
postMessage(message: any, transfer?: any[]): void;
9-
}
10-
11-
const scope = (self as any) as DedicatedWorkerGlobalScope;
12-
13-
export function initProvider(impl) {
1+
export function initProvider(impl, scope = self as any) {
142
scope.onmessage = function(msg) {
153
const data = msg.data;
164
if (data._init) {

src/parser/buffer-json-parser.spec.ts

+12-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { BufferJsonNodeInfo, BufferJsonParser } from './buffer-json-parser';
22

3-
describe('JSON Parser', function() {
3+
describe('Buffer JSON Parser', function() {
44
it('should handle empty input', function() {
55
const instance = new BufferJsonParser('');
66
const info = instance.getRootNodeInfo();
@@ -333,14 +333,14 @@ function expectStringNode(
333333
chars: number,
334334
path: string[]
335335
) {
336-
expect(node.type).toEqual('string', 'Node type');
337-
expect(node.length).toEqual(value.length, 'Node length');
338-
expect(node.chars).toEqual(chars, 'Node chars');
339-
expect(node.path.length).toEqual(path.length, 'Node path length');
336+
expect(node.type).toEqual('string');
337+
expect(node.length).toEqual(value.length);
338+
expect(node.chars).toEqual(chars);
339+
expect(node.path.length).toEqual(path.length);
340340
for (let i = 0; i < path.length; i++) {
341-
expect(node.path[i]).toEqual(path[i], 'Node path pos ' + i);
341+
expect(node.path[i]).toEqual(path[i]);
342342
}
343-
expect(node.getValue()).toEqual(value, 'Node value');
343+
expect(node.getValue()).toEqual(value);
344344
}
345345

346346
function expectNumberNode(
@@ -349,11 +349,11 @@ function expectNumberNode(
349349
chars: number,
350350
path: string[]
351351
) {
352-
expect(node.type).toEqual('number', 'Node type');
353-
expect(node.chars).toEqual(chars, 'Node chars');
354-
expect(node.path.length).toEqual(path.length, 'Node Path Length');
352+
expect(node.type).toEqual('number');
353+
expect(node.chars).toEqual(chars);
354+
expect(node.path.length).toEqual(path.length);
355355
for (let i = 0; i < path.length; i++) {
356-
expect(node.path[i]).toEqual(path[i], 'Node path pos ' + i);
356+
expect(node.path[i]).toEqual(path[i]);
357357
}
358-
expect(node.getValue()).toEqual(value, 'Node value');
358+
expect(node.getValue()).toEqual(value);
359359
}

0 commit comments

Comments
 (0)