From 8af45f900fd73a2d93fc823941c970262ec69fed Mon Sep 17 00:00:00 2001 From: YouWei Zhao Date: Wed, 20 Mar 2019 20:27:23 -0400 Subject: [PATCH] editor_table --- _server/editor_table.js | 337 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 337 insertions(+) create mode 100644 _server/editor_table.js diff --git a/_server/editor_table.js b/_server/editor_table.js new file mode 100644 index 00000000..cd248dad --- /dev/null +++ b/_server/editor_table.js @@ -0,0 +1,337 @@ +editor_table_wrapper = function (editor) { + + editor_table = function () { + + } + + ///////////////////////////////////////////////////////////////////////////// + /** + * HTML模板 + */ + + editor_table.prototype.select = function (options) { + return `\n` + } + editor_table.prototype.option = function (value) { + return `\n` + } + editor_table.prototype.text = function (value) { + return `\n` + } + editor_table.prototype.checkbox = function (value) { + return `\n` + } + editor_table.prototype.textarea = function (value, indent) { + return `\n` + } + + + ///////////////////////////////////////////////////////////////////////////// + /** + * 把来自数据文件的obj和来自*comment.js的commentObj组装成表格 + * commentObj在无视['_data']的意义下与obj同形 + * 即: commentObj['_data']['a']['_data']['b'] 与 obj['a']['b'] 是对应的 + * 在此意义下, 两者的结构是一致的 + * 在commentObj没有被定义的obj的分支, 会取defaultcobj作为默认值 + * 因此在深度优先遍历时,维护 + * field="['a']['b']" + * cfield="['_data']['a']['_data']['b']" + * vobj=obj['a']['b'] + * cobj=commentObj['_data']['a']['_data']['b'] + * cobj + * cobj = Object.assign({}, defaultcobj, pcobj['_data'][ii]) + * 每一项若未定义,就从defaultcobj中取 + * 当其是函数不是具体值时,把args = {field: field, cfield: cfield, vobj: vobj, cobj: cobj}代入算出该值 + * 得到的叶节点的结构如下 + * tr>td[title=field] + * >td[title=comment,cobj=cobj:json] + * >td>div>input[value=thiseval] + * 返回结果 + * 返回一个对象, 假设被命名为tableinfo + * 在把一个 table 的 innerHTML 赋值为 tableinfo.HTML 后 + * 再调 tableinfo.listen(tableinfo.guids) 进行绑定事件 + * @param {Object} obj + * @param {Object} commentObj + * @returns {{"HTML":String,"guids":String[],"listen":Function}} + */ + editor_table.prototype.objToTable_ = function (obj, commentObj) { + // 表格抬头 + var outstr = ["\n条目注释值\n"]; + var guids = []; + var defaultcobj = { + // 默认是文本域 + _type: 'textarea', + _data: '', + _string: function (args) {//object~[field,cfield,vobj,cobj] + var thiseval = args.vobj; + return (typeof (thiseval) === typeof ('')) && thiseval[0] === '"'; + }, + // 默认情况下 非对象和数组的视为叶节点 + _leaf: function (args) {//object~[field,cfield,vobj,cobj] + var thiseval = args.vobj; + if (thiseval == null || thiseval == undefined) return true;//null,undefined + if (typeof (thiseval) === typeof ('')) return true;//字符串 + if (Object.keys(thiseval).length === 0) return true;//数字,true,false,空数组,空对象 + return false; + }, + } + /** + * 深度优先遍历, p*即为父节点的四个属性 + * @param {String} pfield + * @param {String} pcfield + * @param {Object} pvobj + * @param {Object} pcobj + */ + var recursionParse = function (pfield, pcfield, pvobj, pcobj) { + var keysForTableOrder = {}; + var voidMark = {}; + // 1. 按照pcobj排序生成 + if (pcobj && pcobj['_data']) { + for (var ii in pcobj['_data']) keysForTableOrder[ii] = voidMark; + } + // 2. 对每个pvobj且不在pcobj的,再添加到最后 + keysForTableOrder = Object.assign(keysForTableOrder, pvobj) + for (var ii in keysForTableOrder) { + // 3. 对于pcobj有但是pvobj中没有的, 弹出提示, (正常情况下editor_file会补全成null) + // 事实上能执行到这一步工程没崩掉打不开,就继续吧.. + if (keysForTableOrder[ii] === voidMark) { + if (typeof id_815975ad_ee6f_4684_aac7_397b7e392702 === "undefined") { + alert('comment和data不匹配,请在群 HTML5造塔技术交流群 959329661 内反馈') + console.error('comment和data不匹配,请在群 HTML5造塔技术交流群 959329661 内反馈') + id_815975ad_ee6f_4684_aac7_397b7e392702 = 1; + } + pvobj[ii] = null; + } + var field = pfield + "['" + ii + "']"; + var cfield = pcfield + "['_data']['" + ii + "']"; + var vobj = pvobj[ii]; + var cobj = null; + if (pcobj && pcobj['_data'] && pcobj['_data'][ii]) { + // cobj存在时直接取 + cobj = Object.assign({}, defaultcobj, pcobj['_data'][ii]); + } else { + // 当其函数时代入参数算出cobj, 不存在时只取defaultcobj + if (pcobj && (pcobj['_data'] instanceof Function)) cobj = Object.assign({}, defaultcobj, pcobj['_data'](ii)); + else cobj = Object.assign({}, defaultcobj); + } + var args = { field: field, cfield: cfield, vobj: vobj, cobj: cobj } + // 当cobj的参数为函数时,代入args算出值 + for (var key in cobj) { + if (key === '_data') continue; + if (cobj[key] instanceof Function) cobj[key] = cobj[key](args); + } + // 标记为_hide的属性不展示 + if (cobj._hide) continue; + if (!cobj._leaf) { + // 不是叶节点时, 插入展开的标记并继续遍历, 此处可以改成按钮用来添加新项或折叠等 + outstr.push(["--------", field, "\n"].join('')); + recursionParse(field, cfield, vobj, cobj); + } else { + // 是叶节点时, 调objToTr_渲染 + var leafnode = editor_table.objToTr_(obj, commentObj, field, cfield, vobj, cobj); + outstr.push(leafnode[0]); + guids.push(leafnode[1]); + } + } + } + // 开始遍历 + recursionParse("", "", obj, commentObj); + var checkRange = function (cobj, thiseval) { + if (cobj._range) { + return eval(cobj._range); + } + if (cobj._select) { + return cobj._select.values.indexOf(thiseval) !== -1; + } + if (cobj._bool) { + return [true, false].indexOf(thiseval) !== -1; + } + return true; + } + var listen = function (guids) { + // 每个叶节点的事件绑定 + guids.forEach(function (guid) { + // tr>td[title=field] + // >td[title=comment,cobj=cobj:json] + // >td>div>input[value=thiseval] + var thisTr = document.getElementById(guid); + var input = thisTr.children[2].children[0].children[0]; + var field = thisTr.children[0].getAttribute('title'); + var cobj = JSON.parse(thisTr.children[1].getAttribute('cobj')); + var modeNode = thisTr.parentNode; + while (!editor_mode._ids.hasOwnProperty(modeNode.getAttribute('id'))) { + modeNode = modeNode.parentNode; + } + input.onchange = function () { + editor_mode.onmode(editor_mode._ids[modeNode.getAttribute('id')]); + var thiseval = null; + if (input.checked != null) input.value = input.checked; + try { + thiseval = JSON.parse(input.value); + } catch (ee) { + printe(field + ' : ' + ee); + throw ee; + } + if (checkRange(cobj, thiseval)) { + editor_mode.addAction(['change', field, thiseval]); + editor_mode.onmode('save');//自动保存 删掉此行的话点保存按钮才会保存 + } else { + printe(field + ' : 输入的值不合要求,请鼠标放置在注释上查看说明'); + } + } + // 双击表格时 + // 正常编辑: 尝试用事件编辑器或多行文本编辑器打开 + // 添加: 在该项的同一级创建一个内容为null新的项, 刷新后生效并可以继续编辑 + // 删除: 删除该项, 刷新后生效 + // 在点击按钮 添加/删除 后,下一次双击将被视为 添加/删除 + var dblclickfunc = function () { + if (editor_mode.doubleClickMode === 'change') { + if (cobj._type === 'event') editor_blockly.import(guid, { type: cobj._event }); + if (cobj._type === 'textarea') editor_multi.import(guid, { lint: cobj._lint, string: cobj._string }); + } + if (editor_mode.doubleClickMode === 'add') { + editor_mode.doubleClickMode = 'change'; + addfunc() + } + if (editor_mode.doubleClickMode === 'delete') { + editor_mode.doubleClickMode = 'change'; + deletefunc() + } + } + input.ondblclick = dblclickfunc + var doubleClickCheck = [0]; + thisTr.onclick = function () { + var newClick = new Date().getTime(); + var lastClick = doubleClickCheck.shift(); + doubleClickCheck.push(newClick); + if (newClick - lastClick < 500) { + dblclickfunc() + } + } + var deletefunc = function () { + editor_mode.onmode(editor_mode._ids[modeNode.getAttribute('id')]); + if (checkRange(cobj, null)) { + editor_mode.addAction(['delete', field, undefined]); + editor_mode.onmode('save');//自动保存 删掉此行的话点保存按钮才会保存 + } else { + printe(field + ' : 该值不允许为null,无法删除'); + } + } + var addfunc = function () { + editor_mode.onmode(editor_mode._ids[modeNode.getAttribute('id')]); + + var mode = document.getElementById('editModeSelect').value; + + // 1.输入id + var newid = prompt('请输入新项的ID(仅公共事件支持中文ID)'); + if (newid == null || newid.length == 0) { + return; + } + + // 检查commentEvents + if (mode !== 'commonevent') { + // 2.检查id是否符合规范或与已有id重复 + if (!/^[a-zA-Z0-9_]+$/.test(newid)) { + printe('id不符合规范, 请使用大小写字母数字下划线来构成'); + return; + } + } + + var conflict = true; + var basefield = field.replace(/\[[^\[]*\]$/, ''); + if (basefield === "['main']") { + printe("全塔属性 ~ ['main'] 不允许添加新值"); + return; + } + try { + var baseobj = eval('obj' + basefield); + conflict = newid in baseobj; + } catch (ee) { + // 理论上这里不会发生错误 + printe(ee); + throw ee; + } + if (conflict) { + printe('id已存在, 请直接修改该项的值'); + return; + } + // 3.添加 + editor_mode.addAction(['add', basefield + "['" + newid + "']", null]); + editor_mode.onmode('save');//自动保存 删掉此行的话点保存按钮才会保存 + } + }); + } + return { "HTML": outstr.join(''), "guids": guids, "listen": listen }; + } + + /** + * 返回叶节点形如 + * tr>td[title=field] + * >td[title=comment,cobj=cobj:json] + * >td>div>input[value=thiseval] + * 参数意义在 objToTable_ 中已解释 + * @param {Object} obj + * @param {Object} commentObj + * @param {String} field + * @param {String} cfield + * @param {Object} vobj + * @param {Object} cobj + */ + editor_table.prototype.objToTr_ = function (obj, commentObj, field, cfield, vobj, cobj) { + var guid = editor.guid(); + var thiseval = vobj; + var comment = String(cobj._data); + + var charlength = 10; + // "['a']['b']" => "b" + var shortField = field.split("']").slice(-2)[0].split("['").slice(-1)[0]; + // 把长度超过 charlength 的字符改成 固定长度+...的形式 + shortField = (shortField.length < charlength ? shortField : shortField.slice(0, charlength) + '...'); + + // 完整的内容转义后供悬停查看 + var commentHTMLescape = editor.HTMLescape(comment); + // 把长度超过 charlength 的字符改成 固定长度+...的形式 + var shortCommentHTMLescape = (comment.length < charlength ? commentHTMLescape : editor.HTMLescape(comment.slice(0, charlength)) + '...'); + + var cobjstr = Object.assign({}, cobj); + delete cobjstr._data; + // 把cobj塞到第二个td的[cobj]中, 方便绑定事件时取 + cobjstr = editor.HTMLescape(JSON.stringify(cobjstr)); + + var outstr = ['', shortField, '', + '', shortCommentHTMLescape, '', + '
', editor_mode.objToTd_(obj, commentObj, field, cfield, vobj, cobj), '
\n', + ]; + return [outstr.join(''), guid]; + } + + editor_table.prototype.objToTd_ = function (obj, commentObj, field, cfield, vobj, cobj) { + var thiseval = vobj; + if (cobj._select) { + var values = cobj._select.values; + var outstr = [''); + return outstr.join(''); + } else if (cobj._input) { + return ["\n"].join(''); + } else if (cobj._bool) { + return ["\n"].join(''); + } else { + var num = 0;//editor_table.indent(field); + return ["\n'].join(''); + } + } + + editor_table.prototype.indent = function (field) { + var num = '\t'; + if (field.indexOf("['main']") === 0) return 0; + if (field === "['special']") return 0; + return num; + } + + editor.table = new editor_table(); +} +//editor_table_wrapper(editor); \ No newline at end of file