How to avoid "maximum call stack size exceeded" exception? - javascript

I have the next code for generating sitemap tree by urls list.
C# model:
public class Node
{
public Node(string child, string parent)
{
Child = child;
Parent = parent;
}
public string Parent { get; set; }
public string Child { get; set; }
public bool IsRoot { get; set; }
}
C# method that generates list of nodes.
private static List<Node> ExtractNode(List<string> Urls)
{
List<Node> nodeList = new List<Node>();
foreach (string itm in Urls)
{
string[] arr = itm.Split('/');
int index = -1;
foreach (string node in arr)
{
index += 1;
if (index == 0)
{
Node rootNode = new Node(node, "");
if (!nodeList.Exists(x => x.Child == rootNode.Child & x.Parent == rootNode.Parent))
{
rootNode.IsRoot = true;
nodeList.Add(rootNode);
}
}
else
{
Node childNode = new Node(node, arr[index - 1].ToString());
{
if (!nodeList.Exists(x => x.Child == childNode.Child & x.Parent == childNode.Parent))
{
nodeList.Add(childNode);
}
}
}
}
}
return nodeList;
}
Javascript code. Next function takes list of nodes:
function makeTree(nodes) {
var roots = [];
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].IsRoot) {
roots.push(nodes[i].Child);
}
}
var trees = "";
for (var j = 0; j < roots.length; j++) {
trees += "<div class='sitemapRoot'><ul><li>" + getChildren(roots[j], nodes) + "</li></ul></div>";
}
return trees;
}
And next one called recursively:
function getChildren(root, nodes) {
var result = "";
var index = 0;
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].Parent == root) {
index += 1;
}
}
if (index > 0) {
var RootHeader = "";
for (var j = 0; j < nodes.length; j++) {
if (nodes[j].IsRoot & root == nodes[j].Child) {
RootHeader = nodes[j].Child;
}
}
result += RootHeader + "<ul>\n";
for (var k = 0; k < nodes.length; k++) {
if (nodes[k].IsRoot & root == nodes[k].Child) {
RootHeader = nodes[k].Child;
}
if (nodes[k].Parent == root) {
result += "<ul><li>" + nodes[k].Child + getChildren(nodes[k].Child, nodes) + "</li></ul>\n";
}
}
result += "</ul>\n";
return result;
}
return "";
}
This code works good for a small set of data. But when I try to pass list of 500 nodes I get next error:
Uncaught RangeError: Maximum call stack size exceeded
at getChildren (treeGenerator.js:371)
So, the question is how I can optimize code to avoid this error?

There are two ways that you can fix this issue. You always have to worry about the call stack size when you use recursive functions. If you believe it is an issue, then you need to go asynchronous or refactor to not be recursive. These aren't necessarily the most optimized answers but hopefully they'll get you started.
Non-Recursive function
function makeTree(nodes) {
var roots = [],
limbs = [],
i, j;
//go through all of the nodes and link the parent/children.
for (i = 0; i < nodes.length; i++) {
for (j = i + 1; j < nodes.length; j++) {//only look at the rest of the elements to increase performance
if (nodes[i].Child == nodes[j].Parent) {
nodes[i].children = (nodes[i].children || []);
nodes[i].children.push(nodes[j]);
nodes[j].parentNode = nodes[i];
}
}
for (j = 0; j < limbs.length; j++) {//look through the limbs to see if one of them is my child.
if (nodes[i].Child == limbs[j].Parent) {
nodes[i].children = (nodes[i].children || []);
nodes[i].children.push(limbs[j]);
limbs[j].parentNode = nodes[i];
limbs.splice(j--, 1);//remove from the list since I can only have 1 parent.
}
}
//I have all of my children.
if (nodes[i].IsRoot) {
roots.push(nodes[i]);
}else if(!nodes[i].parentNode){
//I don't know my parent so I'm a limb.
limbs.push(node);
}
}
//now that everyone knows their parent and their children, we'll assemble the html by looking at all of the leafs first and working way up the tree.
i = 0;
while(nodes.length > 0){
if(!nodes[i].children || nodes[i].childHtml){
//I'm either a leaf or I have my child's html.
var node = nodes[i];
node.html = node.Child + (node.childHtml? "<ul>" + node.childHtml + "</ul>" : "");
node.childHtml = null;//don't need this anymore.
if(node.parentNode){
//let's check to see if my siblings are complete
var ready = true,
childHtml = "";
for(var j = 0; j < node.parentNode.children.length; j++){
if(node.parentNode.children[j].html == null){
ready = false;//I don't know this html yet so skip it for now.
break;
}else{
childHtml += "<li>" + node.parentNode.children[j].html + "</li>";//go ahead and try to create the list of children.
}
}
if(ready){
//all of siblings are complete, so update parent.
node.parentNode.childHtml = childHtml;
node.parentNode.children = null;//remove reference for cleanup.
}
}
nodes.splice(i, 1);//remove from the list since I have my html.
}else{
i++;//look at the next node in the list.
}
if(i >= nodes.length){
i = 0;//since we are splicing and going through the list multiple times (possibly), we'll set the index back to 0.
}
}
//every node knows it's html, so build the full tree.
var trees = "";
for (var j = 0; j < roots.length; j++) {
trees += "<div class='sitemapRoot'><ul><li>" + roots[j].html + "</li></ul></div>";
}
return trees;
}
Asynchronous Recursive function
function makeTreeAsync(nodes, callback) {
var roots = [],
numRoots = 0;
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].IsRoot) {
numRoots++;
getChildrenAsync(nodes[i], nodes, create);
}
}
function create(child){
roots.push(child);
if(roots.length === numRoots){
var trees = "";
for (var j = 0; j < roots.length; j++) {
trees += "<div class='sitemapRoot'><ul><li>" + roots[j].html + "</li></ul></div>";
}
callback(trees);
}
}
}
function getChildrenAsync(root, nodes, callback) {
var result = "";
var index = 0;
var children = [];
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].Parent == root.Child) {
index += 1;
getChild(i);
}
}
if (index == 0) {
root.html = root.Child;
callback(root);
}
function getChild(x){
setTimeout(function(){
getChildrenAsync(nodes[x], nodes, createHtml);
});
}
function createHtml(node){
children.push(node);
if(children.length === index){
var result = root.Child;
if(children.length){
result += "<ul>"
for (var j = 0; j < children.length; j++) {
result += "<li>" + children[j].html + "</li>";
}
result += "</ul>";
}
root.html = result;
callback(root);
}
}
}
I used the following to create trees for testing the code:
function makeTestNodes(numRoots, numChildrenPerRoot){
var nodes= [];
for(var i = 0;i < numRoots;i++){
nodes.push({
Parent: "",
Child: i.toString(),
IsRoot: 1
});
var parent = i.toString();
for(var j = 0;j < numChildrenPerRoot;j++){
var child = parent + "." + j.toString();
nodes.push({
Parent: parent,
Child: child,
IsRoot: 0
});
nodes.push({
Parent: parent,
Child: parent + "." + j.toString() + "a",
IsRoot: 0
});
parent = child;
}
}
return nodes;
}
Use the following snippet to call the two functions with the test nodes:
Note: the following uses jquery to get the body node. I called the following snippet when the page was ready.
var body = $("body");
body.append(makeTree(makeTestNodes(20, 50)));
makeTreeAsync(makeTestNodes(20, 50), function(html){
body.append(html);
});

Related

problem using .split() method in a function but it works on console.log

the problem is only in the bottom function objectPutter
specifically the line with wowza.split(' '); labelled with the comment
let eq
let x = null
let bracketNum = 0
let k = 0
let pre = 0
class subEqCreator { //subEq object
constructor() {
this.precede = 0;
this.text = '';
}
parser() {
this.text += eq[k]
}
ma() {
this.text.split(' ')
}
};
function trigger() { //used for HTML onClick method
show();
brackets();
subEqDynamic()
parseEquation();
objectPutter()
};
function show() {
const recent = document.querySelector("ol");
const txt = document.getElementById('input');
eq = txt.value;
li = document.createElement('li');
li.innerText = eq;
recent.appendChild(li);
txt.value = '';
};
function brackets() { //counts how many brackets appear
for (let i = 0; i < eq.length; i++) {
if (eq[i] == "(") {
bracketNum++;
};
};
};
let subEqDynamic = function() { // creates a new object for each bracket
for (let count = 0; count <= bracketNum; count++) {
this['subEq' + count] = x = new subEqCreator()
};
};
function parseEquation() { // assign characters to SubEq object
let nextIndex = 0;
let currentIndex = 0;
let lastIndex = [0];
let eqLen = eq.length;
let nex = this['subEq' + nextIndex]
for (k; k < eqLen; k++) {
if (eq[k] == "(") {
nextIndex++;
pre++
this['subEq' + currentIndex].text += '( )';
this['subEq' + nextIndex].precede = pre;
lastIndex.push(currentIndex);
currentIndex = nextIndex;
} else if (eq[k] == ")") {
pre--
currentIndex = lastIndex.pop();
} else {
this['subEq' + currentIndex].parser()
}
}
}
function objectPutter() {
for (let i = 0; i < bracketNum; i++) {
let wowza = this['subEq' + i].text
wowza.split(' '); // 🚩 why isnt it working here
console.log(subEq0);
for (let j = 1; j <= wowza.length; j += 2) { // for loop generates only odds
let ni = i++
wowza.splice(j, 0, this['subEq' + ni])
console.log(i)
}
}
}
to fix this i tried;
making a method for it ma() in the constructor.
putting in the function parseEquation above in case it was a scope issue.
also, i noticed subEq0.split(' ') worked in browser console even replicating it to the way i done it using this['subEq' + i].text.split(' ') where i = 0.
After it runs the it says .splice is not a function and console.log(subEq0) shows subEq0.text is still a string
.split() does not change the variable it returns the splitted variable

JavaScript Json row undefined for TreeView

I have a question. When querying my Json object, although i have my Field as same name and all Json rows available give me an error that is undefined.
//e.g: row.DepParentId
Bellow is my code. Am I missing some tag?
function convert(rows) {
debugger;
function exists(rows, parent) {
for (var i = 0; i < rows.length; i++) {
if (rows[i].DepId === parent) return true;
}
return false;
}
var nodes = [];
// get the top level nodes
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
if (!exists(rows, row.DepParentId)) {
nodes.push({
id: row.DepId,
name: row.Title
});
}
}
var toDo = [];
for (var i = 0; i < nodes.length; i++) {
toDo.push(nodes[i]);
}
while (toDo.length) {
var node = toDo.shift();
// the parent node
// get the children nodes
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
if (row.DepParentId == node.Id) {
var child = {
Id: row.DepId,
Name: row.Title
};
if (node.options) {
node.options.push(child);
} else {
node.options = [child];
}
toDo.push(child);
}
}
}
return nodes;
}
My Json example for one row picked from Firefox
"{"DepId":7,"DepParentId":3,"Title":"OTT"}"
Thanks for helping
Joao
thanks all for helping, it's working now, the problem was the JSON.stringify() was returning only an [Object] so when you use JSON.parse() generates a sintax error.
function onLoaded() {
var itemsCount = items.get_count();
for (var i = 0; i < itemsCount; i++) {
var item = items.itemAt(i);
var dep = JSON.stringify(item.get_fieldValues());
deps.push(dep);
}
So, i had to change the format to be valid to parse like this
var dt = "[" + deps + "]";
var ret = JSON.parse(dt);
Thanks
Joao

Protractor:How to store values in array and then to do sorting

I need to sort list strings under the table ,so for that i have written following lines of code but on console i am not getting any values:
var j = 9;
var rows = element.all(by.repeater('row in renderedRows'));
var column = element.all(by.repeater('col in renderedColumns'));
expect(rows.count()).toEqual(5); //here its printing number of rows
expect(column.count()).toEqual(5); //here its printing number of columns
var arr = [rows.count()];
for (var i = 0; i < rows.count(); i++) {
console.log("aai" + i);
if (i = 0) {
//var columnvalue=column.get(9).getText();
var columnvalue = column.get(9).getText().then(function(ss) {
return ss.trim();
arr[i] = ss.trim(); //here it will save the value first value of column
console.log("value1" + arr[i]);
expect(arr[i]).toEqual('DN');
console.log("aa" + ss.trim());
});
} else {
var j = j + 8;
var columnvalue = column.get(j).getText().then(function(ss) {
return ss.trim();
arr[i] = ss.trim(); //here it will save the other values of column
console.log("value" + arr[i]);
expect(arr[i]).toEqual('DN');
console.log("ab" + ss.trim());
});
}
}
Sorting_Under_Table: function(col){
test = [];
var m;
var dm = 0;
element(by.xpath('//div[#class="ngHeaderScroller"]/div['+col+']')).click();
element.all(by.repeater('row in renderedRows')).then(function(row) {
m = row.length;
for (i = 1; i <= row.length; i++)
{
user_admin_table_name = browser.driver.findElement(by.xpath('//div[#class="ngCanvas"]/div['+i+']/div['+col+']'));
user_admin_table_name.getText().then(function(text) {
var test_var1 = text.toLowerCase().trim();
test.push(test_var1);
var k = test.length
if (k == m){
for (j = 0; j < test.length; j++){
test.sort();
d=j+1;
user_admin_table_name1 = browser.driver.findElement(by.xpath('//div[#class="ngCanvas"]/div['+d+']/div['+col+']'));
user_admin_table_name1.getText().then(function(text1) {
var test_var2 = text1.toLowerCase().trim();
if (test_var2 == test[dm]){
expect(test_var2).toEqual(test[dm]);
dm = dm +1;
}else {
expect(test_var2).toEqual(test[dm]);
log.error("Sorting is not successful");
dm = dm +1;
}
});
}
}
});
}
});
},
You can use this code for sorting and verifying is it sorted or not
I'm not sure how your above example is doing any sorting, but here's a general solution for trimming and then sorting:
var elementsWithTextToSort = element.all(by.xyz...);
elementsWithTextToSort.map(function(elem) {
return elem.getText().then(function(text) {
return text.trim();
});
}).then(function(trimmedTexts) {
return trimmedTexts.sort();
}).then(function(sortedTrimmedTexts) {
//do something with the sorted trimmed texts
});

remove null value in javascript

am having script with working condition but many for loop is there so any way to do this...to simplify this ...am new to script kindly help on this....
function change()
{
//document.getElementById("Geography").options[7]=new Option("", "newval", true, false);
var geo = document.getElementById("Geography").options;
var zon = document.getElementById("zone").options;
var coun = document.getElementById("country").options;
for (var i = 0; i < geo.length; i++)
{
if (geo[i].innerHTML == "Null Value" || geo[i].innerHTML == "")
{
document.getElementById("Geography").options[i] = null;
}
}
for (var i = 0; i < coun.length; i++)
{
alert("Loop1" + i);
if (coun[i].innerHTML == "Null Value")
{
document.getElementById("country").options[i] = null;
}
}
for (var i = 0; i < zon.length; i++)
{
//alert("Loop1" + i);
if (zon[i].innerHTML == "Null Value")
{
document.getElementById("zone").options[i] = null;
}
}
}
To remove an option, call removeChild() on the parent element.
var geoSel = document.getElementById("Geography");
var geo = geoSel.options;
for (var i = geoSel.options.length-1; i >= 0; i--) {
if (geo[i].innerHTML == "Null Value" || geo[i].innerHTML == "") {
geo.removeChild(geo[i]);
}
}
I count down instead of up because removing a child will cause the indexes of all the following children to be shifted down. In a count-up loop, that will cause elements to be skipped.
use this UPDATED DEMO
function change(){
var optionsArr = [];
optionsArr.push(document.getElementById("Geography").options);
optionsArr.push(document.getElementById("zone").options);
optionsArr.push(document.getElementById("country").options);
var optArrlenght = optionsArr.length;
for ( var j = 0; j < optArrlenght; j++){
var options = optionsArr[j];
var optionslength = options.length;
for (var i = 0; i < optionslength; i++)
{
if (options[i].innerHTML == "Null Value" || options[i].innerHTML == "")
{
options[i].remove();
i--;
optionslength--;
}
}
}
}
change();

Remove duplicates from an array which contains array

I have a JSON array like this
[{"Email":"someone#some.com","Name":"ACO","Groups":["MOD_SW","MOD_PI","MOD_GE"],"Id":63,"Url":"aco"},
{"Email":"someone#some.com","Name":"Agpo","Groups":["MOD_PI"],"Id":22,"Url":"agpo"},
{"Email":"someone#some.com","Name":"Akatherm","Groups":["MOD_SW"],"Id":64,"Url":"akatherm"},
{"Email":"someone#some.com","Name":"Albrand","Groups":["MOD_PI,"MOD_GE"],"Id":23,"Url":"albrand"}]
I want to create a new array (for select tag) with distinct Groups.
This Groups is an array.
I want that selectbox to have the following values:
MOD_SW
MOD_PI
MOD_GE
My JS:
UpdateSelectMenu: function (selectId, data) {
$(selectId).empty();
$(selectId).html("<option value='all' selected='selected'>All groups</option>");
var array_unique_values = [];
for (var i = 0; i < data.Groups.length; i++) {
for (var j = i+1; j < data.Groups.length; j++) {
if (data.Groups[i] === data.Groups[j]) {
j = ++i;
}
}
array_unique_values.push(data.Groups[i]);
}
array_unique_values = array_unique_values.sort();
$.each(array_unique_values, function (k, v) {
$(selectId).append("<option value='" + v + "'>" + v + "</option>");
});
}
I tried also
for (var i = 0; i < data.length; i++) { //browse whole data
for (var j = 0; j < data[i].Groups.length; j++) { //browse Groups array
for (var k = j + 1; j < data[i].Groups.length; k++) {
if (data[i].Groups[j] === data[i].Groups[k]) {
continue;
}
}
array_unique_values.push(data[i].Groups[j]);
}
}
But error appears as: Groups.length is null or not an object
This code appends to select tag the Group values but it appears as duplicates because Groups is an array.
I have to create a new for statement to browse the Groups array ?
Or there is another alternative to avoid nested for statements ?
Thank you
2 nested loops could do the job:
var data = [... your data ...];
var groups = [];
$.each(data, function(i, item) {
$.each(item.Groups, function(j, group) {
if ($.inArray(group, groups) == -1) {
groups.push(group);
}
});
});
// at this stage groups = ["MOD_SW", "MOD_PI", "MOD_GE"]
and if you wanted to directly append options to your dropdown:
var groups = [];
$.each(data, function(i, item) {
$.each(item.Groups, function(j, group) {
if ($.inArray(group, groups) == -1) {
groups.push(group);
$(selectId).append(
$('<option/>', {
value: group,
text: group
})
);
}
});
});
UPDATE:
And to make this more efficient you could define a static dtstinct method:
$.extend({
distinct : function(arr) {
var result = [];
$.each(arr, function(index, value) {
if ($.inArray(value, result) == -1) {
result.push(value);
}
});
return result;
}
});
and then use the .map method:
var data = [... your data ...];
var groups = $.distinct($(data).map(function() {
return this.Groups;
}));
$.each(groups, function(index, group) {
$(selectId).append(
$('<option/>', {
value: group,
text: group
})
);
});
First look at the last group: "Groups":["MOD_PI,MOD_GE"]
Don't you need to close the quotes after MOD_PI and open them after the comma or this is a set of groups?
If this with the quotes is problem the your script can look something like this:
var obj = [{"Email":"someone#some.com","Name":"ACO", "Groups":["MOD_SW","MOD_PI","MOD_GE"],"Id":63,"Url":"aco"},
{"Email":"someone#some.com","Name":"Agpo", "Groups":["MOD_PI"],"Id":22,"Url":"agpo"},
{"Email":"someone#some.com","Name":"Akatherm", "Groups":["MOD_SW"],"Id":64,"Url":"akatherm"},
{"Email":"someone#some.com","Name":"Albrand", "Groups":["MOD_PI","MOD_GE"],"Id":23,"Url":"albrand"}]
var temp = {},
result = [];
for (var i = 0; i < obj.length; i+=1) {
for (var j = 0; j < obj[i]['Groups'].length; j+=1) {
if (typeof temp[obj[i]['Groups'][j]] === 'undefined') {
result.push(obj[i]['Groups'][j]);
temp[obj[i]['Groups'][j]] = true;
}
}
}
If this is a set of groups:
for (var i = 0; i < obj.length; i+=1) {
for (var j = 0; j < obj[i]['Groups'].length; j+=1) {
currentGroups = obj[i]['Groups'][j].split(',');
for (var k = 0; k < currentGroups.length; k += 1) {
currentGroups[k] = currentGroups[k].replace(/ /g,"");
if (typeof temp[currentGroups[k]] === 'undefined') {
result.push(currentGroups[k]);
temp[currentGroups[k]] = true;
}
}
}
}
I think that's the most efficient way because you're checking for duplicates with O(1) and you don't have to do extra work (for example sort any array).

Categories

Resources