import './WJsonviewTable.css'
/**
* 前端通過元素展示Json資料為表格,因 [Fork: {@link https://github.com/marianoguerra/json.human.js json.human.js}] 的js與css設定無法進行模組化,自己下載來修改
*
* @export
* @param {Object} jsonObj 輸入Json物件
* @param {Element} rootElem 輸入初始化元素
* @param {Object} [option={}] 輸入設定物件,預設為空物件
* @param {Boolean} [option.showArrayIndex=true] 輸入是否顯示陣列指標,預設為true
* @param {Boolean} [option.hyperlinks.enable=false] 輸入是否產生超連結,預設為false
* @param {Array} [option.hyperlinks.keys=['url']] 輸入產生超連結之key名稱,可輸入多種key,為陣列,預設key為['url']
* @param {String} [option.hyperlinks.target='_blank'] 輸入產生超連結之開啟方式,預設key為'_blank'
* @param {Boolean} [option.bool.showText=false] 輸入是否將boolean顯示為文字,預設為false
* @param {Object} [option.bool.text={true:'Yes',false:'No'}] 輸入將boolean顯示為文字之對應名稱,預設為{true:'Yes',false:'No'}
*/
function WJsonviewTable(jsonObj, rootElem, option) {
let prefixer = makePrefixer('jh')
let p = prefixer
let ARRAY = 1
let BOOL = 2
let INT = 3
let FLOAT = 4
let STRING = 5
let OBJECT = 6
let FUNCTION = 7
let UNK = 99
let STRING_CLASS_NAME = p('type-string')
let STRING_EMPTY_CLASS_NAME = p('type-string') + ' ' + p('empty')
let BOOL_TRUE_CLASS_NAME = p('type-bool-true')
let BOOL_FALSE_CLASS_NAME = p('type-bool-false')
let BOOL_IMAGE = p('type-bool-image')
let INT_CLASS_NAME = p('type-int') + ' ' + p('type-number')
let FLOAT_CLASS_NAME = p('type-float') + ' ' + p('type-number')
let OBJECT_CLASS_NAME = p('type-object')
let OBJ_KEY_CLASS_NAME = p('key') + ' ' + p('object-key')
let OBJ_VAL_CLASS_NAME = p('value') + ' ' + p('object-value')
let OBJ_EMPTY_CLASS_NAME = p('type-object') + ' ' + p('empty')
let FUNCTION_CLASS_NAME = p('type-function')
let ARRAY_KEY_CLASS_NAME = p('key') + ' ' + p('array-key')
let ARRAY_VAL_CLASS_NAME = p('value') + ' ' + p('array-value')
let ARRAY_CLASS_NAME = p('type-array')
let ARRAY_EMPTY_CLASS_NAME = p('type-array') + ' ' + p('empty')
let HYPERLINK_CLASS_NAME = p('a')
let UNKNOWN_CLASS_NAME = p('type-unk')
let indexOf = [].indexOf || function(item) {
for (let i = 0, l = this.length; i < l; i++) {
if (i in this && this[i] === item) return i
} return -1
}
//Init
Init(jsonObj, rootElem, option)
/**
* Initialize
*/
function Init(jsonObj, rootElem, option = {}) {
//clear
rootElem.innerHTML = ''
//add class
rootElem.classList.add('CompCssDJsonViewTable')
//render
let node = format(jsonObj, option)
rootElem.appendChild(node)
}
function makePrefixer(prefix) {
return function(name) {
return prefix + '-' + name
}
}
function isArray(obj) {
return Object.prototype.toString.call(obj) === '[object Array]'
}
function sn(tagName, className, data) {
let result = document.createElement(tagName)
result.className = className
result.appendChild(document.createTextNode('' + data))
return result
}
function scn(tagName, className, child) {
let result = document.createElement(tagName)
let i
let 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) {
let a = scn('a', HYPERLINK_CLASS_NAME, child)
a.setAttribute('href', href)
a.setAttribute('target', target)
return a
}
function getType(obj) {
let 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) {
let result
let container
let key
let keyNode
let valNode
let len
let childs
let tr
let value
let isEmpty = true
let type = getType(data)
// Initialized & used only in case of objects & arrays
let hyperlinksEnabled, aTarget, hyperlinkKeys
switch (type) {
case BOOL: {
let boolOpt = options.bool
container = document.createElement('div')
if (boolOpt.showImage) {
let 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)
: sn('span', BOOL_FALSE_CLASS_NAME, boolOpt.text.false))
}
result = container
break
}
case STRING: {
if (data === '') {
result = sn('span', STRING_EMPTY_CLASS_NAME, '(Empty Text)')
}
else {
result = sn('span', STRING_CLASS_NAME, data)
}
break
}
case INT: {
result = sn('span', INT_CLASS_NAME, data)
break
}
case FLOAT: {
result = sn('span', FLOAT_CLASS_NAME, data)
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]
valNode = _format(value, options, key)
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)')
}
else {
result = scn('table', OBJECT_CLASS_NAME, scn('tbody', '', childs))
}
break
}
case FUNCTION: {
result = sn('span', FUNCTION_CLASS_NAME, data)
break
}
case ARRAY: {
if (data.length > 0) {
childs = []
let 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]
if (hyperlinksEnabled && typeof (value) === 'string') {
valNode = _format(value, options, key)
valNode = scn('td', ARRAY_VAL_CLASS_NAME, linkNode(valNode, value, aTarget))
}
else {
valNode = scn('td', ARRAY_VAL_CLASS_NAME, _format(value, options, key))
}
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)')
}
break
}
default: {
result = sn('span', UNKNOWN_CLASS_NAME, data)
break
}
}
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
}
return options
}
function validateHyperlinkOptions(options) {
let 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 {
let 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 {
let t = boolOptions.text.true
let 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
}
function format(data, options) {
options = validateOptions(options || {})
let result
result = _format(data, options)
result.className = result.className + ' ' + prefixer('root')
return result
}
}
export default WJsonviewTable