jsonview.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. // import {
  2. // isObject,
  3. // getObjectName,
  4. // getType,
  5. // getValuePreview,
  6. // getPreview,
  7. // cssClass,
  8. // createElement
  9. // } from './helpers';
  10. //
  11. // import './style.less';
  12. //
  13. // const DATE_STRING_REGEX = /(^\d{1,4}[\.|\\/|-]\d{1,2}[\.|\\/|-]\d{1,4})(\s*(?:0?[1-9]:[0-5]|1(?=[012])\d:[0-5])\d\s*[ap]m)?$/;
  14. // const PARTIAL_DATE_REGEX = /\d{2}:\d{2}:\d{2} GMT-\d{4}/;
  15. // const JSON_DATE_REGEX = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/;
  16. //
  17. // // When toggleing, don't animated removal or addition of more than a few items
  18. // const MAX_ANIMATED_TOGGLE_ITEMS = 10;
  19. //
  20. // const requestAnimationFrame = window.requestAnimationFrame || function(cb: ()=>void) { cb(); return 0; };
  21. //
  22. // export interface JSONFormatterConfiguration {
  23. // hoverPreviewEnabled?: boolean;
  24. // hoverPreviewArrayCount?: number;
  25. // hoverPreviewFieldCount?: number;
  26. // animateOpen?: boolean;
  27. // animateClose?: boolean;
  28. // theme?: string;
  29. // };
  30. //
  31. // const _defaultConfig: JSONFormatterConfiguration = {
  32. // hoverPreviewEnabled: false,
  33. // hoverPreviewArrayCount: 100,
  34. // hoverPreviewFieldCount: 5,
  35. // animateOpen: true,
  36. // animateClose: true,
  37. // theme: null
  38. // };
  39. //
  40. //
  41. // #<{(|*
  42. // * @class JSONFormatter
  43. // *
  44. // * JSONFormatter allows you to render JSON objects in HTML with a
  45. // * **collapsible** navigation.
  46. // |)}>#
  47. // export default class JSONFormatter {
  48. //
  49. // // Hold the open state after the toggler is used
  50. // private _isOpen: boolean = null;
  51. //
  52. // // A reference to the element that we render to
  53. // private element: Element;
  54. //
  55. // #<{(|*
  56. // * @param {object} json The JSON object you want to render. It has to be an
  57. // * object or array. Do NOT pass raw JSON string.
  58. // *
  59. // * @param {number} [open=1] his number indicates up to how many levels the
  60. // * rendered tree should expand. Set it to `0` to make the whole tree collapsed
  61. // * or set it to `Infinity` to expand the tree deeply
  62. // *
  63. // * @param {object} [config=defaultConfig] -
  64. // * defaultConfig = {
  65. // * hoverPreviewEnabled: false,
  66. // * hoverPreviewArrayCount: 100,
  67. // * hoverPreviewFieldCount: 5
  68. // * }
  69. // *
  70. // * Available configurations:
  71. // * #####Hover Preview
  72. // * * `hoverPreviewEnabled`: enable preview on hover
  73. // * * `hoverPreviewArrayCount`: number of array items to show in preview Any
  74. // * array larger than this number will be shown as `Array[XXX]` where `XXX`
  75. // * is length of the array.
  76. // * * `hoverPreviewFieldCount`: number of object properties to show for object
  77. // * preview. Any object with more properties that thin number will be
  78. // * truncated.
  79. // *
  80. // * @param {string} [key=undefined] The key that this object in it's parent
  81. // * context
  82. // |)}>#
  83. // constructor(public json: any, private open = 1, private config: JSONFormatterConfiguration = _defaultConfig, private key?: string) {
  84. //
  85. // // Setting default values for config object
  86. // if (this.config.hoverPreviewEnabled === undefined) {
  87. // this.config.hoverPreviewEnabled = _defaultConfig.hoverPreviewEnabled;
  88. // }
  89. // if (this.config.hoverPreviewArrayCount === undefined) {
  90. // this.config.hoverPreviewArrayCount = _defaultConfig.hoverPreviewArrayCount;
  91. // }
  92. // if (this.config.hoverPreviewFieldCount === undefined) {
  93. // this.config.hoverPreviewFieldCount = _defaultConfig.hoverPreviewFieldCount;
  94. // }
  95. // }
  96. //
  97. // #<{(|
  98. // * is formatter open?
  99. // |)}>#
  100. // private get isOpen(): boolean {
  101. // if (this._isOpen !== null) {
  102. // return this._isOpen;
  103. // } else {
  104. // return this.open > 0;
  105. // }
  106. // }
  107. //
  108. // #<{(|
  109. // * set open state (from toggler)
  110. // |)}>#
  111. // private set isOpen(value: boolean) {
  112. // this._isOpen = value;
  113. // }
  114. //
  115. // #<{(|
  116. // * is this a date string?
  117. // |)}>#
  118. // private get isDate(): boolean {
  119. // return (this.type === 'string') &&
  120. // (DATE_STRING_REGEX.test(this.json) ||
  121. // JSON_DATE_REGEX.test(this.json) ||
  122. // PARTIAL_DATE_REGEX.test(this.json));
  123. // }
  124. //
  125. // #<{(|
  126. // * is this a URL string?
  127. // |)}>#
  128. // private get isUrl(): boolean {
  129. // return this.type === 'string' && (this.json.indexOf('http') === 0);
  130. // }
  131. //
  132. // #<{(|
  133. // * is this an array?
  134. // |)}>#
  135. // private get isArray(): boolean {
  136. // return Array.isArray(this.json);
  137. // }
  138. //
  139. // #<{(|
  140. // * is this an object?
  141. // * Note: In this context arrays are object as well
  142. // |)}>#
  143. // private get isObject(): boolean {
  144. // return isObject(this.json);
  145. // }
  146. //
  147. // #<{(|
  148. // * is this an empty object with no properties?
  149. // |)}>#
  150. // private get isEmptyObject(): boolean {
  151. // return !this.keys.length && !this.isArray;
  152. // }
  153. //
  154. // #<{(|
  155. // * is this an empty object or array?
  156. // |)}>#
  157. // private get isEmpty(): boolean {
  158. // return this.isEmptyObject || (this.keys && !this.keys.length && this.isArray);
  159. // }
  160. //
  161. // #<{(|
  162. // * did we recieve a key argument?
  163. // * This means that the formatter was called as a sub formatter of a parent formatter
  164. // |)}>#
  165. // private get hasKey(): boolean {
  166. // return typeof this.key !== 'undefined';
  167. // }
  168. //
  169. // #<{(|
  170. // * if this is an object, get constructor function name
  171. // |)}>#
  172. // private get constructorName(): string {
  173. // return getObjectName(this.json);
  174. // }
  175. //
  176. // #<{(|
  177. // * get type of this value
  178. // * Possible values: all JavaScript primitive types plus "array" and "null"
  179. // |)}>#
  180. // private get type(): string {
  181. // return getType(this.json);
  182. // }
  183. //
  184. // #<{(|
  185. // * get object keys
  186. // * If there is an empty key we pad it wit quotes to make it visible
  187. // |)}>#
  188. // private get keys(): string[] {
  189. // if (this.isObject) {
  190. // return Object.keys(this.json).map((key)=> key ? key : '""');
  191. // } else {
  192. // return [];
  193. // }
  194. // }
  195. //
  196. // #<{(|*
  197. // * Toggles `isOpen` state
  198. // *
  199. // |)}>#
  200. // toggleOpen() {
  201. // this.isOpen = !this.isOpen;
  202. //
  203. // if (this.element) {
  204. // if (this.isOpen) {
  205. // this.appendChildren(this.config.animateOpen);
  206. // } else{
  207. // this.removeChildren(this.config.animateClose);
  208. // }
  209. // this.element.classList.toggle(cssClass('open'));
  210. // }
  211. // }
  212. //
  213. // #<{(|*
  214. // * Open all children up to a certain depth.
  215. // * Allows actions such as expand all/collapse all
  216. // *
  217. // |)}>#
  218. // openAtDepth(depth = 1) {
  219. // if (depth < 0) {
  220. // return;
  221. // }
  222. //
  223. // this.open = depth;
  224. // this.isOpen = (depth !== 0);
  225. //
  226. // if (this.element) {
  227. // this.removeChildren(false);
  228. //
  229. // if (depth === 0) {
  230. // this.element.classList.remove(cssClass('open'));
  231. // } else {
  232. // this.appendChildren(this.config.animateOpen);
  233. // this.element.classList.add(cssClass('open'));
  234. // }
  235. // }
  236. // }
  237. //
  238. // #<{(|*
  239. // * Generates inline preview
  240. // *
  241. // * @returns {string}
  242. // |)}>#
  243. // getInlinepreview() {
  244. // if (this.isArray) {
  245. //
  246. // // if array length is greater then 100 it shows "Array[101]"
  247. // if (this.json.length > this.config.hoverPreviewArrayCount) {
  248. // return `Array[${this.json.length}]`;
  249. // } else {
  250. // return `[${this.json.map(getPreview).join(', ')}]`;
  251. // }
  252. // } else {
  253. //
  254. // const keys = this.keys;
  255. //
  256. // // the first five keys (like Chrome Developer Tool)
  257. // const narrowKeys = keys.slice(0, this.config.hoverPreviewFieldCount);
  258. //
  259. // // json value schematic information
  260. // const kvs = narrowKeys.map(key => `${key}:${getPreview(this.json[key])}`);
  261. //
  262. // // if keys count greater then 5 then show ellipsis
  263. // const ellipsis = keys.length >= this.config.hoverPreviewFieldCount ? '…' : '';
  264. //
  265. // return `{${kvs.join(', ')}${ellipsis}}`;
  266. // }
  267. // }
  268. //
  269. //
  270. // #<{(|*
  271. // * Renders an HTML element and installs event listeners
  272. // *
  273. // * @returns {HTMLDivElement}
  274. // |)}>#
  275. // render(): HTMLDivElement {
  276. //
  277. // // construct the root element and assign it to this.element
  278. // this.element = createElement('div', 'row');
  279. //
  280. // // construct the toggler link
  281. // const togglerLink = createElement('a', 'toggler-link');
  282. //
  283. // // if this is an object we need a wrapper span (toggler)
  284. // if (this.isObject) {
  285. // togglerLink.appendChild(createElement('span', 'toggler'));
  286. // }
  287. //
  288. // // if this is child of a parent formatter we need to append the key
  289. // if (this.hasKey) {
  290. // togglerLink.appendChild(createElement('span', 'key', `${this.key}:`));
  291. // }
  292. //
  293. // // Value for objects and arrays
  294. // if (this.isObject) {
  295. //
  296. // // construct the value holder element
  297. // const value = createElement('span', 'value');
  298. //
  299. // // we need a wrapper span for objects
  300. // const objectWrapperSpan = createElement('span');
  301. //
  302. // // get constructor name and append it to wrapper span
  303. // var constructorName = createElement('span', 'constructor-name', this.constructorName);
  304. // objectWrapperSpan.appendChild(constructorName);
  305. //
  306. // // if it's an array append the array specific elements like brackets and length
  307. // if (this.isArray) {
  308. // const arrayWrapperSpan = createElement('span');
  309. // arrayWrapperSpan.appendChild(createElement('span', 'bracket', '['));
  310. // arrayWrapperSpan.appendChild(createElement('span', 'number', (this.json.length)));
  311. // arrayWrapperSpan.appendChild(createElement('span', 'bracket', ']'));
  312. // objectWrapperSpan.appendChild(arrayWrapperSpan);
  313. // }
  314. //
  315. // // append object wrapper span to toggler link
  316. // value.appendChild(objectWrapperSpan);
  317. // togglerLink.appendChild(value);
  318. //
  319. // // Primitive values
  320. // } else {
  321. //
  322. // // make a value holder element
  323. // const value = this.isUrl ? createElement('a') : createElement('span');
  324. //
  325. // // add type and other type related CSS classes
  326. // value.classList.add(cssClass(this.type));
  327. // if (this.isDate) {
  328. // value.classList.add(cssClass('date'));
  329. // }
  330. // if (this.isUrl) {
  331. // value.classList.add(cssClass('url'));
  332. // value.setAttribute('href', this.json);
  333. // }
  334. //
  335. // // Append value content to value element
  336. // const valuePreview = getValuePreview(this.json, this.json);
  337. // value.appendChild(document.createTextNode(valuePreview));
  338. //
  339. // // append the value element to toggler link
  340. // togglerLink.appendChild(value);
  341. // }
  342. //
  343. // // if hover preview is enabled, append the inline preview element
  344. // if (this.isObject && this.config.hoverPreviewEnabled) {
  345. // const preview = createElement('span', 'preview-text');
  346. // preview.appendChild(document.createTextNode(this.getInlinepreview()));
  347. // togglerLink.appendChild(preview);
  348. // }
  349. //
  350. // // construct a children element
  351. // const children = createElement('div', 'children');
  352. //
  353. // // set CSS classes for children
  354. // if (this.isObject) {
  355. // children.classList.add(cssClass('object'));
  356. // }
  357. // if (this.isArray) {
  358. // children.classList.add(cssClass('array'));
  359. // }
  360. // if (this.isEmpty) {
  361. // children.classList.add(cssClass('empty'));
  362. // }
  363. //
  364. // // set CSS classes for root element
  365. // if (this.config && this.config.theme) {
  366. // this.element.classList.add(cssClass(this.config.theme));
  367. // }
  368. // if (this.isOpen) {
  369. // this.element.classList.add(cssClass('open'));
  370. // }
  371. //
  372. // // append toggler and children elements to root element
  373. // this.element.appendChild(togglerLink);
  374. // this.element.appendChild(children);
  375. //
  376. // // if formatter is set to be open call appendChildren
  377. // if (this.isObject && this.isOpen) {
  378. // this.appendChildren();
  379. // }
  380. //
  381. // // add event listener for toggling
  382. // if (this.isObject) {
  383. // togglerLink.addEventListener('click', this.toggleOpen.bind(this));
  384. // }
  385. //
  386. // return this.element as HTMLDivElement;
  387. // }
  388. //
  389. // #<{(|*
  390. // * Appends all the children to children element
  391. // * Animated option is used when user triggers this via a click
  392. // |)}>#
  393. // appendChildren(animated = false) {
  394. // const children = this.element.querySelector(`div.${cssClass('children')}`);
  395. //
  396. // if (!children || this.isEmpty) { return; }
  397. //
  398. // if (animated) {
  399. // let index = 0;
  400. // const addAChild = ()=> {
  401. // const key = this.keys[index];
  402. // const formatter = new JSONFormatter(this.json[key], this.open - 1, this.config, key);
  403. // children.appendChild(formatter.render());
  404. //
  405. // index += 1;
  406. //
  407. // if (index < this.keys.length) {
  408. // if (index > MAX_ANIMATED_TOGGLE_ITEMS) {
  409. // addAChild();
  410. // } else {
  411. // requestAnimationFrame(addAChild);
  412. // }
  413. // }
  414. // };
  415. //
  416. // requestAnimationFrame(addAChild);
  417. //
  418. // } else {
  419. // this.keys.forEach(key => {
  420. // const formatter = new JSONFormatter(this.json[key], this.open - 1, this.config, key);
  421. // children.appendChild(formatter.render());
  422. // });
  423. // }
  424. // }
  425. //
  426. // #<{(|*
  427. // * Removes all the children from children element
  428. // * Animated option is used when user triggers this via a click
  429. // |)}>#
  430. // removeChildren(animated = false) {
  431. // const childrenElement = this.element.querySelector(`div.${cssClass('children')}`) as HTMLDivElement;
  432. //
  433. // if (animated) {
  434. // let childrenRemoved = 0;
  435. // const removeAChild = ()=> {
  436. // if (childrenElement && childrenElement.children.length) {
  437. // childrenElement.removeChild(childrenElement.children[0]);
  438. // childrenRemoved += 1;
  439. // if (childrenRemoved > MAX_ANIMATED_TOGGLE_ITEMS) {
  440. // removeAChild();
  441. // } else {
  442. // requestAnimationFrame(removeAChild);
  443. // }
  444. // }
  445. // };
  446. // requestAnimationFrame(removeAChild);
  447. // } else {
  448. // if (childrenElement) {
  449. // childrenElement.innerHTML = '';
  450. // }
  451. // }
  452. // }
  453. // }