I have a datatable in Razor Project with rows containing id, name, title, parent, and other fields. I need to bind a JavaScript array dynamically, but I tried this code and got an error. Please help.
It works fine with static data like below, and my organization chart is shown with no error.
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 },
];
With the generated data from JSON doesn't work and shows an error:
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));
//document.getElementsByClassName('org-del-button')[0].style.display = 'none';
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>"; // keep vertical lines aligned if there's only 1 child
}
else if (i == 0) {
linesCols += "<td class='line left'></td>"; // the first cell doesn't have a line in the top
}
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>
You can access the Model
in JavaScript directly without iterating over each DataRow
in the @foreach
statement. Then, you deserialise from the DataTable
into a list of objects.
Aside, it would be better to return the data as an array instead of a DataTable
in the model.
In your model class:
public class GetDataTableInJsModel
{
public DataTable Table_Public { get; set; }
// Optional: Define the model class instead of dynamic
public List<dynamic> Table_Public_List { get; set; }
}
While in your controller action:
var dt = <Your DataTable instance>;
return View(new GetDataTableInJsModel
{
Table_Public = dt,
Table_Public_List = JsonConvert.DeserializeObject(
JsonConvert.SerializeObject(dt)
)
});
To display the JSON string in the alert dialog, you should serialize the data
with JSON.stringfy()
.
@using Newtonsoft.Json
<script type="text/javascript">
var data = @Html.Raw(
JsonConvert.DeserializeObject(JsonConvert.SerializeObject(Model.Table_Public))
);
// For using Model.Table_Public_List
// var data = @Html.Raw(Model.Table_Public_List);
alert(JSON.stringify(data));
</script>