/*globals define, module, require, document*/ (function (root, factory) { "use strict"; if (typeof define === 'function' && define.amd) { define([], factory); } else if (typeof module !== 'undefined' && module.exports) { module.exports = factory(); } else { root.JsonHuman = factory(); } }(this, function () { "use strict"; var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; function makePrefixer(prefix) { return function (name) { return prefix + "-" + name; }; } function isArray(obj) { return toString.call(obj) === '[object Array]'; } function sn(tagName, className, data, keypath) { var result = document.createElement(tagName); result.className = className; if (keypath) { result.setAttribute('data-keypath', keypath); } result.appendChild(document.createTextNode("" + data)); return result; } function scn(tagName, className, child) { var result = document.createElement(tagName), i, len; result.className = className; if (isArray(child)) { for (i = 0, len = child.length; i < len; i += 1) { result.appendChild(child[i]); } } else { result.appendChild(child); } return result; } function linkNode(child, href, target){ var a = scn("a", HYPERLINK_CLASS_NAME, child); a.setAttribute('href', href); a.setAttribute('target', target); return a; } var toString = Object.prototype.toString, prefixer = makePrefixer("jh"), p = prefixer, ARRAY = 1, BOOL = 2, INT = 3, FLOAT = 4, STRING = 5, OBJECT = 6, FUNCTION = 7, UNK = 99, STRING_CLASS_NAME = p("type-string"), STRING_EMPTY_CLASS_NAME = p("type-string") + " " + p("empty"), BOOL_TRUE_CLASS_NAME = p("type-bool-true"), BOOL_FALSE_CLASS_NAME = p("type-bool-false"), BOOL_IMAGE = p("type-bool-image"), INT_CLASS_NAME = p("type-int") + " " + p("type-number"), FLOAT_CLASS_NAME = p("type-float") + " " + p("type-number"), OBJECT_CLASS_NAME = p("type-object"), OBJ_KEY_CLASS_NAME = p("key") + " " + p("object-key"), OBJ_VAL_CLASS_NAME = p("value") + " " + p("object-value"), OBJ_EMPTY_CLASS_NAME = p("type-object") + " " + p("empty"), FUNCTION_CLASS_NAME = p("type-function"), ARRAY_KEY_CLASS_NAME = p("key") + " " + p("array-key"), ARRAY_VAL_CLASS_NAME = p("value") + " " + p("array-value"), ARRAY_CLASS_NAME = p("type-array"), ARRAY_EMPTY_CLASS_NAME = p("type-array") + " " + p("empty"), HYPERLINK_CLASS_NAME = p('a'), UNKNOWN_CLASS_NAME = p("type-unk"); function getType(obj) { var type = typeof obj; switch (type) { case "boolean": return BOOL; case "string": return STRING; case "number": return (obj % 1 === 0) ? INT : FLOAT; case "function": return FUNCTION; default: if (isArray(obj)) { return ARRAY; } else if (obj === Object(obj)) { return OBJECT; } else { return UNK; } } } function _format(data, options, parentKey, keypath) { var result, container, key, keyNode, valNode, len, childs, tr, value, isEmpty = true, accum = [], type = getType(data); // Initialized & used only in case of objects & arrays var hyperlinksEnabled, aTarget, hyperlinkKeys ; switch (type) { case BOOL: var boolOpt = options.bool; container = document.createElement('div'); if(boolOpt.showImage) { var img = document.createElement('img'); img.setAttribute('class', BOOL_IMAGE); img.setAttribute('src', '' + (data ? boolOpt.img.true : boolOpt.img.false)); container.appendChild(img); } if(boolOpt.showText){ container.appendChild(data ? sn("span", BOOL_TRUE_CLASS_NAME, boolOpt.text.true, keypath) : sn("span", BOOL_FALSE_CLASS_NAME, boolOpt.text.false, keypath)); } result = container; break; case STRING: if (data === "") { result = sn("span", STRING_EMPTY_CLASS_NAME, "(Empty Text)", keypath); } else { result = sn("span", STRING_CLASS_NAME, data, keypath); } break; case INT: result = sn("span", INT_CLASS_NAME, data, keypath); break; case FLOAT: result = sn("span", FLOAT_CLASS_NAME, data, keypath); break; case OBJECT: childs = []; aTarget = options.hyperlinks.target; hyperlinkKeys = options.hyperlinks.keys; // Is Hyperlink Key hyperlinksEnabled = options.hyperlinks.enable && hyperlinkKeys && hyperlinkKeys.length > 0; for (key in data) { isEmpty = false; value = data[key]; var objectKeypath = keypath ? keypath + "." + key : key; valNode = _format(value, options, key, objectKeypath); keyNode = sn("th", OBJ_KEY_CLASS_NAME, key); if( hyperlinksEnabled && typeof(value) === 'string' && indexOf.call(hyperlinkKeys, key) >= 0){ valNode = scn("td", OBJ_VAL_CLASS_NAME, linkNode(valNode, value, aTarget)); } else { valNode = scn("td", OBJ_VAL_CLASS_NAME, valNode); } tr = document.createElement("tr"); tr.appendChild(keyNode); tr.appendChild(valNode); childs.push(tr); } if (isEmpty) { result = sn("span", OBJ_EMPTY_CLASS_NAME, "(Empty Object)", keypath); } else { result = scn("table", OBJECT_CLASS_NAME, scn("tbody", '', childs)); } break; case FUNCTION: result = sn("span", FUNCTION_CLASS_NAME, data, keypath); break; case ARRAY: if (data.length > 0) { childs = []; var showArrayIndices = options.showArrayIndex; aTarget = options.hyperlinks.target; hyperlinkKeys = options.hyperlinks.keys; // Hyperlink of arrays? hyperlinksEnabled = parentKey && options.hyperlinks.enable && hyperlinkKeys && hyperlinkKeys.length > 0 && indexOf.call(hyperlinkKeys, parentKey) >= 0; for (key = 0, len = data.length; key < len; key += 1) { keyNode = sn("th", ARRAY_KEY_CLASS_NAME, key); value = data[key]; var arrayKeypath = keypath + "[" + key + "]"; if(hyperlinksEnabled && typeof(value) === "string") { valNode = _format(value, options, key, arrayKeypath); valNode = scn("td", ARRAY_VAL_CLASS_NAME, linkNode(valNode, value, aTarget)); } else { valNode = scn("td", ARRAY_VAL_CLASS_NAME, _format(value, options, key, arrayKeypath)); } tr = document.createElement("tr"); if(showArrayIndices) { tr.appendChild(keyNode); } tr.appendChild(valNode); childs.push(tr); } result = scn("table", ARRAY_CLASS_NAME, scn("tbody", '', childs)); } else { result = sn("span", ARRAY_EMPTY_CLASS_NAME, "(Empty List)", keypath); } break; default: result = sn("span", UNKNOWN_CLASS_NAME, data, keypath); break; } return result; } function format(data, options) { options = validateOptions(options || {}); var result; result = _format(data, options); result.className = result.className + " " + prefixer("root"); return result; } function validateOptions(options){ options = validateArrayIndexOption(options); options = validateHyperlinkOptions(options); options = validateBoolOptions(options); // Add any more option validators here return options; } function validateArrayIndexOption(options) { if(options.showArrayIndex === undefined){ options.showArrayIndex = true; } else { // Force to boolean just in case options.showArrayIndex = options.showArrayIndex ? true: false; } return options; } function validateHyperlinkOptions(options){ var hyperlinks = { enable : false, keys : null, target : '' }; if(options.hyperlinks && options.hyperlinks.enable) { hyperlinks.enable = true; hyperlinks.keys = isArray(options.hyperlinks.keys) ? options.hyperlinks.keys : []; if(options.hyperlinks.target) { hyperlinks.target = '' + options.hyperlinks.target; } else { hyperlinks.target = '_blank'; } } options.hyperlinks = hyperlinks; return options; } function validateBoolOptions(options){ if(!options.bool){ options.bool = { text: { true : "true", false : "false" }, img : { true: "", false: "" }, showImage : false, showText : true }; } else { var boolOptions = options.bool; // Show text if no option if(!boolOptions.showText && !boolOptions.showImage){ boolOptions.showImage = false; boolOptions.showText = true; } if(boolOptions.showText){ if(!boolOptions.text){ boolOptions.text = { true : "true", false : "false" }; } else { var t = boolOptions.text.true, f = boolOptions.text.false; if(getType(t) != STRING || t === ''){ boolOptions.text.true = 'true'; } if(getType(f) != STRING || f === ''){ boolOptions.text.false = 'false'; } } } if(boolOptions.showImage){ if(!boolOptions.img.true && !boolOptions.img.false){ boolOptions.showImage = false; } } } return options; } return { format: format }; }));