Razor项目中DataTable转JS数组绑定组织图表报错求助
Razor项目中动态绑定DataTable到组织图表报错问题
问题背景
在Razor项目里,有一个包含id、name、title、parent及其他字段的DataTable,需要动态绑定到JavaScript数组渲染组织图表。静态数据可正常显示图表,但通过JSON生成动态数据时出现报错。
静态可运行数据示例
var testData = [ { id: 1, name: "John", title: "CEO", parent: 0 }, { id: 2, name: "Tom", title: "SellManager", parent: 1 }, { id: 3, name: "Jerry", title: "SupportManager", parent: 1 }, { id: 4, name: "Robert", title: "SellExpert", parent: 2 }, ];
报错信息
Uncaught TypeError: Cannot read properties of undefined (reading 'render') jquery-3.7.1.min.js:2 jQuery.Deferred exception: Cannot read properties of undefined (reading 'render') TypeError: Cannot read properties of undefined (reading 'render')
相关代码
<script type="text/javascript"> ///////////////////// Chart Script Start ///////////////////// var jq = jQuery.noConflict(); (function (jq) { jq.fn.orgChart = function (options) { var opts = jq.extend({}, jq.fn.orgChart.defaults, options); return new OrgChart(jq(this), opts); } jq.fn.orgChart.defaults = { data: [{ id: 1, name: 'Root', title: 'Amount', parent: 0, hierarchy: 0 }], showControls: false, allowEdit: false, onAddNode: null, onDeleteNode: null, onClickNode: null, newNodeText: 'Add', delNodeText: 'del' }; function OrgChart(jqcontainer, opts) { var data = opts.data; var nodes = {}; var rootNodes = []; this.opts = opts; this.jqcontainer = jqcontainer; var self = this; this.draw = function () { jqcontainer.empty().append(rootNodes[0].render(opts)); jqcontainer.find('.node').click(function () { if (self.opts.onClickNode !== null) { self.opts.onClickNode(nodes[jq(this).attr('node-id')]); } }); if (opts.allowEdit) { jqcontainer.find('.node h2').click(function (e) { var thisId = jq(this).parent().attr('node-id'); self.startEdit(thisId); e.stopPropagation(); }); jqcontainer.find('.node h3').click(function (e) { var thisId = jq(this).parent().attr('node-id'); self.startEdith3(thisId); e.stopPropagation(); }); } // add "add button" listener jqcontainer.find('.org-add-button').click(function (e) { var thisId = jq(this).parent().attr('node-id'); if (self.opts.onAddNode !== null) { self.opts.onAddNode(nodes[thisId]); } else { self.newNode(thisId); } e.stopPropagation(); }); jqcontainer.find('.org-del-button').click(function (e) { var thisId = jq(this).parent().attr('node-id'); if (self.opts.onDeleteNode !== null) { self.opts.onDeleteNode(nodes[thisId]); } else { self.deleteNode(thisId); } e.stopPropagation(); }); } this.startEdit = function (id) { var inputElement = jq('<input class="org-input" type="text" value="'+nodes[id].data.name+'"/>'); jqcontainer.find('div[node-id=' + id + '] h2').replaceWith(inputElement); var commitChange = function () { var h2Element = jq('<h2>' + nodes[id].data.name + '</h2>'); if (opts.allowEdit) { h2Element.click(function () { self.startEdit(id); }) } inputElement.replaceWith(h2Element); } inputElement.focus(); inputElement.keyup(function (event) { if (event.which == 13) { commitChange(); } else { nodes[id].data.name = inputElement.val(); } }); inputElement.blur(function (event) { commitChange(); }) } //////////////////////title field////////////////////////////// this.startEdith3 = function (id) { var inputElement = jq('<input class="org-input" type="text" value="' + nodes[id].data.title + '"/>'); jqcontainer.find('div[node-id=' + id + '] h3').replaceWith(inputElement); var commitChange = function () { var h3Element = jq('<h3>' + nodes[id].data.title + '</h3>'); if (opts.allowEdit) { h3Element.click(function () { self.startEdith3(id); }) } inputElement.replaceWith(h3Element); } inputElement.focus(); inputElement.keyup(function (event) { if (event.which == 13) { commitChange(); } else { nodes[id].data.title = inputElement.val(); } }); inputElement.blur(function (event) { commitChange(); }) } //////////////////////title field ends//////////////////////// this.newNode = function (parentId) { var nextId = Object.keys(nodes).length; while (nextId in nodes) { nextId++; } self.addNode({ id: nextId, name: '(New name)', title: '0.0', hierarchy: parentId, parent: parentId }); } this.addNode = function (data) { var newNode = new Node(data); nodes[data.id] = newNode; nodes[data.parent].addChild(newNode); self.draw(); self.startEdit(data.id); self.startEdith3(data.id); } this.deleteNode = function (id) { for (var i = 0; i < nodes[id].children.length; i++) { self.deleteNode(nodes[id].children[i].data.id); } nodes[nodes[id].data.parent].removeChild(id); delete nodes[id]; self.draw(); } this.getData = function () { var outData = []; for (var i in nodes) { outData.push(nodes[i].data); } return outData; } // constructor for (var i in data) { var node = new Node(data[i]); nodes[data[i].id] = node; } // generate parent child tree for (var i in nodes) { if (nodes[i].data.parent == 0) { rootNodes.push(nodes[i]); } else { nodes[nodes[i].data.parent].addChild(nodes[i]); } } // draw org chart jqcontainer.addClass('orgChart'); self.draw(); } function Node(data) { this.data = data; this.children = []; var self = this; this.addChild = function (childNode) { this.children.push(childNode); } this.removeChild = function (id) { for (var i = 0; i < self.children.length; i++) { if (self.children[i].data.id == id) { self.children.splice(i, 1); return; } } } this.render = function (opts) { var childLength = self.children.length, mainTable; mainTable = "<table cellpadding='0' cellspacing='0' border='0'>"; var nodeColspan = childLength > 0 ? 2 * childLength : 2; mainTable += "<tr><td colspan='" + nodeColspan + "'>" + self.formatNode(opts) + "</td></tr>"; if (childLength > 0) { var downLineTable = "<table cellpadding='0' cellspacing='0' border='0'><tr class='lines x'><td class='line left half'></td><td class='line right half'></td></table>"; mainTable += "<tr class='lines'><td colspan='" + childLength * 2 + "'>" + downLineTable + '</td></tr>'; var linesCols = ''; for (var i = 0; i < childLength; i++) { if (childLength == 1) { linesCols += "<td class='line left half'></td>"; } else if (i == 0) { linesCols += "<td class='line left'></td>"; } else { linesCols += "<td class='line left top'></td>"; } if (childLength == 1) { linesCols += "<td class='line right half'></td>"; } else if (i == childLength - 1) { linesCols += "<td class='line right'></td>"; } else { linesCols += "<td class='line right top'></td>"; } } mainTable += "<tr class='lines v'>" + linesCols + "</tr>"; mainTable += "<tr>"; for (var i in self.children) { mainTable += "<td colspan='2'>" + self.children[i].render(opts) + "</td>"; } mainTable += "</tr>"; } mainTable += '</table>'; return mainTable; } this.formatNode = function (opts) { var nameString = '', descString = ''; titleString = ''; if (typeof data.name !== 'undefined') { nameString = '<h2>' + self.data.name + '</h2>'; } if (typeof data.title !== 'undefined') { titleString = '<h3>' + self.data.title + '</h3>'; } if (typeof data.description !== 'undefined') { descString = '<p>' + self.data.description + '</p>'; } if (opts.showControls) { var buttonsHtml = "<div class='org-add-button'>" + opts.newNodeText + "</div><div class='org-del-button'>" + opts.delNodeText + "</div>"; } else { buttonsHtml = ''; } return "<div class='node' node-id='" + this.data.id + "'>" + nameString + titleString + descString + buttonsHtml + "</div>"; } } })(jQuery); var data1 = @Html.Raw(JsonConvert.DeserializeObject(JsonConvert.SerializeObject(Model.Table_Public))); testData = JSON.stringify(data1); alert(testData); jq(function () { org_chart = jq('#orgChart').orgChart({ data: testData, showControls: false, allowEdit: false, onAddNode: function (node) { log('Created new node on node ' + node.data.id); org_chart.newNode(node.data.id); }, onDeleteNode: function (node) { log('Deleted node ' + node.data.id); org_chart.deleteNode(node.data.id); }, onClickNode: function (node) { log('Clicked node ' + node.data.id); } }); }); </script>
解决方案
核心问题
报错原因是传入orgChart的data参数是JSON字符串,而非JavaScript对象数组。静态数据是直接定义的数组,动态生成时却用JSON.stringify(data1)把对象数组转成了字符串,导致OrgChart构造函数无法正确处理数据。
修复步骤
- 移除多余的序列化操作:
@Html.Raw(JsonConvert.SerializeObject(Model.Table_Public))已经直接生成了符合要求的JavaScript对象数组,不需要再用JSON.stringify转换。 - 校验数据结构:确保
Model.Table_Public返回的每条数据包含id、name、title、parent字段,且存在parent=0的根节点(否则rootNodes为空,调用rootNodes[0].render时仍会报错)。
修改后的代码片段
// 替换原动态数据生成代码 var testData = @Html.Raw(JsonConvert.SerializeObject(Model.Table_Public)); // 直接使用对象数组,无需JSON.stringify转换
额外检查点
- 确认
Model.Table_Public中存在parent=0的根节点,避免rootNodes为空。 - 检查所有节点的
parent对应的id确实存在,防止出现找不到父节点的情况。
内容的提问来源于stack exchange,提问作者test testb
相关产品推荐
相关产品推荐

