123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819 |
- /**
- * JSON Tree library (a part of jsonTreeViewer)
- * http://github.com/summerstyle/jsonTreeViewer
- *
- * Copyright 2017 Vera Lobacheva (http://iamvera.com)
- * Released under the MIT license (LICENSE.txt)
- */
- var jsonTree = (function() {
-
- /* ---------- Utilities ---------- */
- var utils = {
-
- /*
- * Returns js-"class" of value
- *
- * @param val {any type} - value
- * @returns {string} - for example, "[object Function]"
- */
- getClass : function(val) {
- return Object.prototype.toString.call(val);
- },
-
- /**
- * Checks for a type of value (for valid JSON data types).
- * In other cases - throws an exception
- *
- * @param val {any type} - the value for new node
- * @returns {string} ("object" | "array" | "null" | "boolean" | "number" | "string")
- */
- getType : function(val) {
- if (val === null) {
- return 'null';
- }
-
- switch (typeof val) {
- case 'number':
- return 'number';
-
- case 'string':
- return 'string';
-
- case 'boolean':
- return 'boolean';
- }
-
- switch(utils.getClass(val)) {
- case '[object Array]':
- return 'array';
-
- case '[object Object]':
- return 'object';
- }
-
- throw new Error('Bad type: ' + utils.getClass(val));
- },
-
- /**
- * Applies for each item of list some function
- * and checks for last element of the list
- *
- * @param obj {Object | Array} - a list or a dict with child nodes
- * @param func {Function} - the function for each item
- */
- forEachNode : function(obj, func) {
- var type = utils.getType(obj),
- isLast;
-
- switch (type) {
- case 'array':
- isLast = obj.length - 1;
-
- obj.forEach(function(item, i) {
- func(i, item, i === isLast);
- });
-
- break;
-
- case 'object':
- var keys = Object.keys(obj).sort();
-
- isLast = keys.length - 1;
-
- keys.forEach(function(item, i) {
- func(item, obj[item], i === isLast);
- });
-
- break;
- }
-
- },
-
- /**
- * Implements the kind of an inheritance by
- * using parent prototype and
- * creating intermediate constructor
- *
- * @param Child {Function} - a child constructor
- * @param Parent {Function} - a parent constructor
- */
- inherits : (function() {
- var F = function() {};
-
- return function(Child, Parent) {
- F.prototype = Parent.prototype;
- Child.prototype = new F();
- Child.prototype.constructor = Child;
- };
- })(),
-
- /*
- * Checks for a valid type of root node*
- *
- * @param {any type} jsonObj - a value for root node
- * @returns {boolean} - true for an object or an array, false otherwise
- */
- isValidRoot : function(jsonObj) {
- switch (utils.getType(jsonObj)) {
- case 'object':
- case 'array':
- return true;
- default:
- return false;
- }
- },
- /**
- * Extends some object
- */
- extend : function(targetObj, sourceObj) {
- for (var prop in sourceObj) {
- if (sourceObj.hasOwnProperty(prop)) {
- targetObj[prop] = sourceObj[prop];
- }
- }
- }
- };
-
-
- /* ---------- Node constructors ---------- */
-
- /**
- * The factory for creating nodes of defined type.
- *
- * ~~~ Node ~~~ is a structure element of an onject or an array
- * with own label (a key of an object or an index of an array)
- * and value of any json data type. The root object or array
- * is a node without label.
- * {...
- * [+] "label": value,
- * ...}
- *
- * Markup:
- * <li class="jsontree_node [jsontree_node_expanded]">
- * <span class="jsontree_label-wrapper">
- * <span class="jsontree_label">
- * <span class="jsontree_expand-button" />
- * "label"
- * </span>
- * :
- * </span>
- * <(div|span) class="jsontree_value jsontree_value_(object|array|boolean|null|number|string)">
- * ...
- * </(div|span)>
- * </li>
- *
- * @param label {string} - key name
- * @param val {Object | Array | string | number | boolean | null} - a value of node
- * @param isLast {boolean} - true if node is last in list of siblings
- *
- * @return {Node}
- */
- function Node(label, val, isLast) {
- var nodeType = utils.getType(val);
-
- if (nodeType in Node.CONSTRUCTORS) {
- return new Node.CONSTRUCTORS[nodeType](label, val, isLast);
- } else {
- throw new Error('Bad type: ' + utils.getClass(val));
- }
- }
-
- Node.CONSTRUCTORS = {
- 'boolean' : NodeBoolean,
- 'number' : NodeNumber,
- 'string' : NodeString,
- 'null' : NodeNull,
- 'object' : NodeObject,
- 'array' : NodeArray
- };
-
-
- /*
- * The constructor for simple types (string, number, boolean, null)
- * {...
- * [+] "label": value,
- * ...}
- * value = string || number || boolean || null
- *
- * Markup:
- * <li class="jsontree_node">
- * <span class="jsontree_label-wrapper">
- * <span class="jsontree_label">"age"</span>
- * :
- * </span>
- * <span class="jsontree_value jsontree_value_(number|boolean|string|null)">25</span>
- * ,
- * </li>
- *
- * @abstract
- * @param label {string} - key name
- * @param val {string | number | boolean | null} - a value of simple types
- * @param isLast {boolean} - true if node is last in list of parent childNodes
- */
- function _NodeSimple(label, val, isLast) {
- if (this.constructor === _NodeSimple) {
- throw new Error('This is abstract class');
- }
-
- var self = this,
- el = document.createElement('li'),
- labelEl,
- template = function(label, val) {
- var str = '\
- <span class="jsontree_label-wrapper">\
- <span class="jsontree_label">"' +
- label +
- '"</span> : \
- </span>\
- <span class="jsontree_value-wrapper">\
- <span class="jsontree_value jsontree_value_' + self.type + '">' +
- val +
- '</span>' +
- (!isLast ? ',' : '') +
- '</span>';
-
- return str;
- };
-
- self.label = label;
- self.isComplex = false;
-
- el.classList.add('jsontree_node');
- el.innerHTML = template(label, val);
-
- self.el = el;
- labelEl = el.querySelector('.jsontree_label');
-
- labelEl.addEventListener('click', function(e) {
- if (e.altKey) {
- self.toggleMarked();
- return;
- }
- if (e.shiftKey) {
- document.getSelection().removeAllRanges();
- alert(self.getJSONPath());
- return;
- }
- }, false);
- }
- _NodeSimple.prototype = {
- constructor : _NodeSimple,
- /**
- * Mark node
- */
- mark : function() {
- this.el.classList.add('jsontree_node_marked');
- },
- /**
- * Unmark node
- */
- unmark : function() {
- this.el.classList.remove('jsontree_node_marked');
- },
- /**
- * Mark or unmark node
- */
- toggleMarked : function() {
- this.el.classList.toggle('jsontree_node_marked');
- },
- /**
- * Expands parent node of this node
- *
- * @param isRecursive {boolean} - if true, expands all parent nodes
- * (from node to root)
- */
- expandParent : function(isRecursive) {
- if (!this.parent) {
- return;
- }
-
- this.parent.expand();
- this.parent.expandParent(isRecursive);
- },
- /**
- * Returns JSON-path of this
- *
- * @param isInDotNotation {boolean} - kind of notation for returned json-path
- * (by default, in bracket notation)
- * @returns {string}
- */
- getJSONPath : function(isInDotNotation) {
- if (this.isRoot) {
- return "$";
- }
- var currentPath;
- if (this.parent.type === 'array') {
- currentPath = "[" + this.label + "]";
- } else {
- currentPath = isInDotNotation ? "." + this.label : "['" + this.label + "']";
- }
- return this.parent.getJSONPath(isInDotNotation) + currentPath;
- }
- };
-
-
- /*
- * The constructor for boolean values
- * {...
- * [+] "label": boolean,
- * ...}
- * boolean = true || false
- *
- * @constructor
- * @param label {string} - key name
- * @param val {boolean} - value of boolean type, true or false
- * @param isLast {boolean} - true if node is last in list of parent childNodes
- */
- function NodeBoolean(label, val, isLast) {
- this.type = "boolean";
-
- _NodeSimple.call(this, label, val, isLast);
- }
- utils.inherits(NodeBoolean,_NodeSimple);
-
-
- /*
- * The constructor for number values
- * {...
- * [+] "label": number,
- * ...}
- * number = 123
- *
- * @constructor
- * @param label {string} - key name
- * @param val {number} - value of number type, for example 123
- * @param isLast {boolean} - true if node is last in list of parent childNodes
- */
- function NodeNumber(label, val, isLast) {
- this.type = "number";
-
- _NodeSimple.call(this, label, val, isLast);
- }
- utils.inherits(NodeNumber,_NodeSimple);
-
-
- /*
- * The constructor for string values
- * {...
- * [+] "label": string,
- * ...}
- * string = "abc"
- *
- * @constructor
- * @param label {string} - key name
- * @param val {string} - value of string type, for example "abc"
- * @param isLast {boolean} - true if node is last in list of parent childNodes
- */
- function NodeString(label, val, isLast) {
- this.type = "string";
-
- _NodeSimple.call(this, label, '"' + val + '"', isLast);
- }
- utils.inherits(NodeString,_NodeSimple);
-
-
- /*
- * The constructor for null values
- * {...
- * [+] "label": null,
- * ...}
- *
- * @constructor
- * @param label {string} - key name
- * @param val {null} - value (only null)
- * @param isLast {boolean} - true if node is last in list of parent childNodes
- */
- function NodeNull(label, val, isLast) {
- this.type = "null";
-
- _NodeSimple.call(this, label, val, isLast);
- }
- utils.inherits(NodeNull,_NodeSimple);
-
-
- /*
- * The constructor for complex types (object, array)
- * {...
- * [+] "label": value,
- * ...}
- * value = object || array
- *
- * Markup:
- * <li class="jsontree_node jsontree_node_(object|array) [expanded]">
- * <span class="jsontree_label-wrapper">
- * <span class="jsontree_label">
- * <span class="jsontree_expand-button" />
- * "label"
- * </span>
- * :
- * </span>
- * <div class="jsontree_value">
- * <b>{</b>
- * <ul class="jsontree_child-nodes" />
- * <b>}</b>
- * ,
- * </div>
- * </li>
- *
- * @abstract
- * @param label {string} - key name
- * @param val {Object | Array} - a value of complex types, object or array
- * @param isLast {boolean} - true if node is last in list of parent childNodes
- */
- function _NodeComplex(label, val, isLast) {
- if (this.constructor === _NodeComplex) {
- throw new Error('This is abstract class');
- }
-
- var self = this,
- el = document.createElement('li'),
- template = function(label, sym) {
- var comma = (!isLast) ? ',' : '',
- str = '\
- <div class="jsontree_value-wrapper">\
- <div class="jsontree_value jsontree_value_' + self.type + '">\
- <b>' + sym[0] + '</b>\
- <span class="jsontree_show-more">…</span>\
- <ul class="jsontree_child-nodes"></ul>\
- <b>' + sym[1] + '</b>' +
- '</div>' + comma +
- '</div>';
-
- if (label !== null) {
- str = '\
- <span class="jsontree_label-wrapper">\
- <span class="jsontree_label">' +
- '<span class="jsontree_expand-button"></span>' +
- '"' + label +
- '"</span> : \
- </span>' + str;
- }
-
- return str;
- },
- childNodesUl,
- labelEl,
- moreContentEl,
- childNodes = [];
-
- self.label = label;
- self.isComplex = true;
-
- el.classList.add('jsontree_node');
- el.classList.add('jsontree_node_complex');
- el.innerHTML = template(label, self.sym);
-
- childNodesUl = el.querySelector('.jsontree_child-nodes');
-
- if (label !== null) {
- labelEl = el.querySelector('.jsontree_label');
- moreContentEl = el.querySelector('.jsontree_show-more');
-
- labelEl.addEventListener('click', function(e) {
- if (e.altKey) {
- self.toggleMarked();
- return;
- }
- if (e.shiftKey) {
- document.getSelection().removeAllRanges();
- alert(self.getJSONPath());
- return;
- }
- self.toggle(e.ctrlKey || e.metaKey);
- }, false);
-
- moreContentEl.addEventListener('click', function(e) {
- self.toggle(e.ctrlKey || e.metaKey);
- }, false);
-
- self.isRoot = false;
- } else {
- self.isRoot = true;
- self.parent = null;
-
- el.classList.add('jsontree_node_expanded');
- }
-
- self.el = el;
- self.childNodes = childNodes;
- self.childNodesUl = childNodesUl;
-
- utils.forEachNode(val, function(label, node, isLast) {
- self.addChild(new Node(label, node, isLast));
- });
-
- self.isEmpty = !Boolean(childNodes.length);
- if (self.isEmpty) {
- el.classList.add('jsontree_node_empty');
- }
- }
- utils.inherits(_NodeComplex, _NodeSimple);
-
- utils.extend(_NodeComplex.prototype, {
- constructor : _NodeComplex,
-
- /*
- * Add child node to list of child nodes
- *
- * @param child {Node} - child node
- */
- addChild : function(child) {
- this.childNodes.push(child);
- this.childNodesUl.appendChild(child.el);
- child.parent = this;
- },
-
- /*
- * Expands this list of node child nodes
- *
- * @param isRecursive {boolean} - if true, expands all child nodes
- */
- expand : function(isRecursive){
- if (this.isEmpty) {
- return;
- }
-
- if (!this.isRoot) {
- this.el.classList.add('jsontree_node_expanded');
- }
-
- if (isRecursive) {
- this.childNodes.forEach(function(item, i) {
- if (item.isComplex) {
- item.expand(isRecursive);
- }
- });
- }
- },
-
- /*
- * Collapses this list of node child nodes
- *
- * @param isRecursive {boolean} - if true, collapses all child nodes
- */
- collapse : function(isRecursive) {
- if (this.isEmpty) {
- return;
- }
-
- if (!this.isRoot) {
- this.el.classList.remove('jsontree_node_expanded');
- }
-
- if (isRecursive) {
- this.childNodes.forEach(function(item, i) {
- if (item.isComplex) {
- item.collapse(isRecursive);
- }
- });
- }
- },
-
- /*
- * Expands collapsed or collapses expanded node
- *
- * @param {boolean} isRecursive - Expand all child nodes if this node is expanded
- * and collapse it otherwise
- */
- toggle : function(isRecursive) {
- if (this.isEmpty) {
- return;
- }
-
- this.el.classList.toggle('jsontree_node_expanded');
-
- if (isRecursive) {
- var isExpanded = this.el.classList.contains('jsontree_node_expanded');
-
- this.childNodes.forEach(function(item, i) {
- if (item.isComplex) {
- item[isExpanded ? 'expand' : 'collapse'](isRecursive);
- }
- });
- }
- },
- /**
- * Find child nodes that match some conditions and handle it
- *
- * @param {Function} matcher
- * @param {Function} handler
- * @param {boolean} isRecursive
- */
- findChildren : function(matcher, handler, isRecursive) {
- if (this.isEmpty) {
- return;
- }
-
- this.childNodes.forEach(function(item, i) {
- if (matcher(item)) {
- handler(item);
- }
- if (item.isComplex && isRecursive) {
- item.findChildren(matcher, handler, isRecursive);
- }
- });
- }
- });
-
-
- /*
- * The constructor for object values
- * {...
- * [+] "label": object,
- * ...}
- * object = {"abc": "def"}
- *
- * @constructor
- * @param label {string} - key name
- * @param val {Object} - value of object type, {"abc": "def"}
- * @param isLast {boolean} - true if node is last in list of siblings
- */
- function NodeObject(label, val, isLast) {
- this.sym = ['{', '}'];
- this.type = "object";
-
- _NodeComplex.call(this, label, val, isLast);
- }
- utils.inherits(NodeObject,_NodeComplex);
-
-
- /*
- * The constructor for array values
- * {...
- * [+] "label": array,
- * ...}
- * array = [1,2,3]
- *
- * @constructor
- * @param label {string} - key name
- * @param val {Array} - value of array type, [1,2,3]
- * @param isLast {boolean} - true if node is last in list of siblings
- */
- function NodeArray(label, val, isLast) {
- this.sym = ['[', ']'];
- this.type = "array";
-
- _NodeComplex.call(this, label, val, isLast);
- }
- utils.inherits(NodeArray, _NodeComplex);
-
-
- /* ---------- The tree constructor ---------- */
-
- /*
- * The constructor for json tree.
- * It contains only one Node (Array or Object), without property name.
- * CSS-styles of .tree define main tree styles like font-family,
- * font-size and own margins.
- *
- * Markup:
- * <ul class="jsontree_tree clearfix">
- * {Node}
- * </ul>
- *
- * @constructor
- * @param jsonObj {Object | Array} - data for tree
- * @param domEl {DOMElement} - DOM-element, wrapper for tree
- */
- function Tree(jsonObj, domEl) {
- this.wrapper = document.createElement('ul');
- this.wrapper.className = 'jsontree_tree clearfix';
-
- this.rootNode = null;
-
- this.sourceJSONObj = jsonObj;
- this.loadData(jsonObj);
- this.appendTo(domEl);
- }
-
- Tree.prototype = {
- constructor : Tree,
-
- /**
- * Fill new data in current json tree
- *
- * @param {Object | Array} jsonObj - json-data
- */
- loadData : function(jsonObj) {
- if (!utils.isValidRoot(jsonObj)) {
- alert('The root should be an object or an array');
- return;
- }
- this.sourceJSONObj = jsonObj;
-
- this.rootNode = new Node(null, jsonObj, 'last');
- this.wrapper.innerHTML = '';
- this.wrapper.appendChild(this.rootNode.el);
- },
-
- /**
- * Appends tree to DOM-element (or move it to new place)
- *
- * @param {DOMElement} domEl
- */
- appendTo : function(domEl) {
- domEl.appendChild(this.wrapper);
- },
-
- /**
- * Expands all tree nodes (objects or arrays) recursively
- *
- * @param {Function} filterFunc - 'true' if this node should be expanded
- */
- expand : function(filterFunc) {
- if (this.rootNode.isComplex) {
- if (typeof filterFunc == 'function') {
- this.rootNode.childNodes.forEach(function(item, i) {
- if (item.isComplex && filterFunc(item)) {
- item.expand();
- }
- });
- } else {
- this.rootNode.expand('recursive');
- }
- }
- },
-
- /**
- * Collapses all tree nodes (objects or arrays) recursively
- */
- collapse : function() {
- if (typeof this.rootNode.collapse === 'function') {
- this.rootNode.collapse('recursive');
- }
- },
- /**
- * Returns the source json-string (pretty-printed)
- *
- * @param {boolean} isPrettyPrinted - 'true' for pretty-printed string
- * @returns {string} - for exemple, '{"a":2,"b":3}'
- */
- toSourceJSON : function(isPrettyPrinted) {
- if (!isPrettyPrinted) {
- return JSON.stringify(this.sourceJSONObj);
- }
- var DELIMETER = "[%^$#$%^%]",
- jsonStr = JSON.stringify(this.sourceJSONObj, null, DELIMETER);
- jsonStr = jsonStr.split("\n").join("<br />");
- jsonStr = jsonStr.split(DELIMETER).join(" ");
- return jsonStr;
- },
- /**
- * Find all nodes that match some conditions and handle it
- */
- findAndHandle : function(matcher, handler) {
- this.rootNode.findChildren(matcher, handler, 'isRecursive');
- },
- /**
- * Unmark all nodes
- */
- unmarkAll : function() {
- this.rootNode.findChildren(function(node) {
- return true;
- }, function(node) {
- node.unmark();
- }, 'isRecursive');
- }
- };
-
- /* ---------- Public methods ---------- */
- return {
- /**
- * Creates new tree by data and appends it to the DOM-element
- *
- * @param jsonObj {Object | Array} - json-data
- * @param domEl {DOMElement} - the wrapper element
- * @returns {Tree}
- */
- create : function(jsonObj, domEl) {
- return new Tree(jsonObj, domEl);
- }
- };
- })();
|