Recursive function is not capturing parent DOM node attributes - javascript

I wrote a recursive function that traverses nested DOM nodes of the following form:
<a href="#" title="test">
<div id="nested-image">
<img src="image.jpg" />
</div>
</a>
The recursive function is the following:
function getNestedNodes(nodeList) {
var ary = [];
for(var i = 0; i < nodeList.length; ++i) {
var myJSONChildren = {};
if(nodeList[i].childElementCount) {
var htmlCollection = nodeList[i].children;
for(var j = 0; j < htmlCollection.length; ++j) {
for(var k =0, attrs = htmlCollection[j].attributes, l = attrs.length; k < l; ++k) {
myJSONChildren['tag'] = htmlCollection[j].nodeName;
myJSONChildren[attrs.item(k).nodeName] = attrs.item(k).nodeValue;
};
}
myJSONChildren['children'] = getNestedNodes(htmlCollection);
ary.push(myJSONChildren);
};
}
return ary;
}
so if I call that function this way:
var links = document.querySelectorAll('a');
console.log(JSON.stringify(getNestedNodes(links)));
it should return a JSON array of the following form:
[{
tag:'a',
href:"#",
title:"test",
children:[{
tag:"div",
id:"nested-image",
children:[{
tag:"img",
src:"image.jpg"
}]
}]
}]
}]
However, it is only returning one of the following form:
[{
"tag":"DIV",
"id":"nested-image",
"children":[{
"tag":"IMG",
"src":"https://www.gravatar.com/avatar/d1a336ae4b6876a4c5c044ec17876ce0",
"children":[]
}]
}]
and I haven't been able to get the form that I want in a proper way without getting empty results, or duplicate results.
Also, I'd like to optimize my recursive function, I'm sure that I can be refactored into something more readable.
Here's a fiddle for you to see:
http://jsfiddle.net/DfHqv/
Any help will be greatly appreciated!

The problem is that you've made your function to anticipate receiving a collection, yet you're looping two node collections within.
So it seems that you're not pushing in the nodes from the outer loop, but only the children. This would explain why you're not getting the top level.
Just keep it to a single loop of nodes (and then the one for attributes, of course), and then instead of looping the children within a new loop, just pass it in the recursive call.
function getNestedNodes(nodeList) {
var ary = [];
// Loop the collection of nodes
for(var i = 0; i < nodeList.length; ++i) {
var node = nodeList[i];
// Create the new object with the "tag" populated
var jsonNode = {"tag":node.nodeName};
// Add the attributes to the object
for(var k =0, attrs = node.attributes, l = attrs.length; k < l; ++k) {
jsonNode[attrs.item(k).nodeName] = attrs.item(k).nodeValue;
}
// Make a recursive call if any children are present, and add the result
if (node.children && node.children.length)
jsonNode['children'] = getNestedNodes(node.children);
ary.push(jsonNode);
}
return ary;
}
DEMO: http://jsfiddle.net/argA3/1/
[
{
"tag": "A",
"title": "test",
"href": "#",
"children": [
{
"tag": "DIV",
"id": "nested-image",
"children": [
{
"tag": "IMG",
"src": "image.jpg"
}
]
}
]
}
]

This seems to work:
function getNestedNodes(nodeList) {
var ary = [];
for (var i = 0; i < nodeList.length; i += 1) {
var attributes = {};
for (var key in nodeList[i].attributes) {
attributes[nodeList[i].attributes.item(key).nodeName] = nodeList[i].attributes.item(key).nodeValue;
}
ary.push({
tag: nodeList[i].nodeName,
attributes: attributes,
children: getNestedNodes(nodeList[i].children)
});
}
return ary;
}
var links = document.querySelectorAll('a');
console.log(JSON.stringify(getNestedNodes(links)));
Output:
[{"tag":"A","attributes":{"href":"#","title":"test"},"children":[{"tag":"DIV","attributes":{"id":"nested-image"},"children":[{"tag":"IMG","attributes":{"src":"https://www.gravatar.com/avatar/d1a336ae4b6876a4c5c044ec17876ce0?s=32&d=identicon&r=PG"},"children":[]}]}]}]

Since we're all prividing various (refactored) bids:
// getNestedNodes2(nodeList)
// #nodeList: a collection of nodes to serialize
function getNestedNodes2(nodeList){
// iterate over the node collection
for (var i = 0, result = []; i < nodeList.length; i++){
// begin building a definition of the current node
var thisNode = {
tag: nodeList[i].tagName
};
// iterate over any attributes on the current node and add them
// to the current definition.
for (var j = 0, attributes = nodeList[i].attributes; j < attributes.length; j++){
thisNode[attributes.item(j).nodeName] = attributes.item(j).nodeValue;
}
// check for child elements and, if present, also add them to
// the definition
if (nodeList[i].childElementCount > 0){
thisNode.children = getNestedNodes2(nodeList[i].children);
}
// add the definition to the results set
result.push(thisNode);
}
// return the results
return result;
}
and the end result:
[
{
"tag": "A",
"title": "test",
"href": "#",
"children": [
{
"tag": "DIV",
"id": "nested-image",
"children": [
{
"tag": "IMG",
"src": "https://www.gravatar.com/avatar/d1a336ae4b6876a4c5c044ec17876ce0?s=32&d=identicon&r=PG"
}
]
}
]
}
]
Keep in mind you're accepting a collection in, which means the nodes in that first call are ignored when you go right in to iterating over them and only building a definition of the children. Instead, work on the nodes within the received collection first, then have recursion take care of the children.

Related

Find whether an array has a substring corresponding to another element in a new array JS

I get the value of "name" from a promise and I store it in an array as it MAY contain multiple names.
var name = ["Arun_Manohar"];
var combinations = ["abc", "def", "ghi", "har", "lmn"];
Lets say I have an activities object.
var activities = {
driving: false,
jogging: false,
drinking: false
}
How do I go about writing a check to update the activities object in this case.
NOTE: No two elements in combinations array will be a substring of names array. Just one.
Case: Since name contains a substring of one of the elements of combinations, I need to update the activities object. If the name contains a substring like "abc", I will update the object with different values.
Use of lodash would be great.
Tried something and wasnt able to update the object.
if(name.indexOf(_.filter(combinations, function(e) { return.indexOf("har") !== -1})) !== -1) {
activities.driving= true;
activities.jogging= true;
}
If the name contains "abcpoc", since the substring is present in combinations array, I update the object with a different set of values.
If you're using lodash, you can use _.intersectionWith, using a comparator function that uses indexOf().
matched_combos = _.intersectionWith(combinations, names, (combo, name) => name.indexOf(combo) != -1);
Then you can loop through matched_combos to add appropriate things to activities:
matched_combos.forEach(str => {
switch(str) {
"har":
activities.driving = true;
activities.jogging = true;
break;
...
}
});
var names = ["Arun_Manohar"];
var combos = ["abc", "def", "ghi", "har", "lmn"];
var activities = {};
for (var i = 0; i < names.length; i++) {
for (var j = 0; j < combos.length; j++) {
if (names[i].indexOf(combos[j]) >= 0) {
activities.driving = true; //or whatever you need here;
}
}
}
Yo can also extend this code with additional checks and continue/break statements to avoid additional unnecessary loops if corresponding substring was already found.
Here is ES5 solution with indexOf:
var names = ["Arun_Manohar"];
var combinations = ["abc", "def", "ghi", "har", "lmn"];
var activities = {
driving: false,
jogging: false,
drinking: false
}
for (var i = 0; i < names.length; i++) {
for (var j = 0; j < combinations.length; j++) {
if(names[i].indexOf(combinations[j]) >= 0) {
activities.name = names[i]
}
}
}
console.log(activities);
And here is ES6 solution with includes:
var names = ["Arun_Manohar"];
var combinations = ["abc", "def", "ghi", "har", "lmn"];
var activities = {
driving: false,
jogging: false,
drinking: false
}
for (var i = 0; i < names.length; i++) {
for (var j = 0; j < combinations.length; j++) {
if(names[i].includes(combinations[j])) {
activities.name = names[i]
}
}
}
console.log(activities);
And here is more elegant ES6 solution with includes, forEach iterating method and arrow functions:
var names = ["Arun_Manohar"];
var combinations = ["abc", "def", "ghi", "har", "lmn"];
var activities = {
driving: false,
jogging: false,
drinking: false
}
names.forEach(item1 => {
combinations.forEach(item2 => {
if(item1.includes(item2)) {
activities.name = item1;
}
});
});
console.log(activities);

Javascript returns only keys but not values

The following is the data from the source file:
{
"dubbuseqchapter+block#a7a5931f68d0482eaff2b7c9f9684e47": {
"category": "chapter",
"children": [
"dubbuseqsequential+block#968513c8f0cc4249b7cfc2290ac967dc",
"dubbuseqsequential+block#f7f730a478144a74bd127f996d6dc4f5",
"dubbuseqsequential+block#91a0d5d7cd9649a3bdf057400e0a1c96",
"dubbuseqsequential+block#28b2b171b6734b13af29735796c5ad5a",
"dubbuseqsequential+block#192a150c8aab43b9bd236773ba60b414",
"dubbuseqsequential+block#26b3464dad42460ea66f9afe89770065"
],
"metadata": {
"display_name": "Introduction course orientation"
}
},
"dubbuseqchapter+block#b2451e9195c5466db8b66f53ed06c9fd": {
"category": "chapter",
"children": [
"dubbuseqsequential+block#c95826a16f71405ba58319d23d250fc4",
"dubbuseqsequential+block#fe4e3b8b7cdd4fa0b9fe9090223b7125",
"dubbuseqsequential+block#44bbdee625dc465ebe725d2126ed0662",
"dubbuseqsequential+block#8d4daba07d4443f3b2a0b2506280ee2c",
"dubbuseqsequential+block#c68d9d3ba7de45b1b0770085e4f1f286",
"dubbuseqsequential+block#ccdca5b2aca94dbdabb3a57a75adf3fa"
],
"metadata": {
"display_name": "Module closing section"
}
}
}
The following javascript brings the top key values (i.e dubbuseqchapter+block#a7a5931f68d0482eaff2b7c9f9684e47,dubbuseqchapter+block#b2451e9195c5466db8b66f53ed06c9fd )
Javascript code
var obj = JSON.parse(jContent);
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
var row = createRowCopy(getOutputRowMeta().size());
var idx = getInputRowMeta().size();
row[idx++] = keys[i];
// Alert (keys.length);
putRow(row);
}
However, I am unable to get the values of the keys..(i.e. Category, Children and metadata) in this example.
I have tried Objects.values() but it returns null or object object in the Alert.
keys is an array of strings, each string being a property name.
You get the value for a property name in the usual way:
object[property_name]
i.e.
var value = obj[keys[i]];
this code shows how to navigate into parsed JsonData
var obj = JSON.parse(textJson);
var keys = Object.keys(obj);
console.log(obj[keys[0]].metadata.display_name);
this will print : Introduction course orientation
Or even this way to retrieve your subProperties
var obj = JSON.parse(textJson);
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++){
console.log(keys[i]);
var subKeys = Object.keys(obj[keys[i]]);
for (var j = 0; j < subKeys.length; j++) console.log(subKeys[j] + " --> " + obj[keys[i]][subKeys[j]]);
}

remove array element using javascript

I am new to javascript & jquery, Need to remove an element from the below array structure
[{
"tag": "tag1",
"description": "description1"
}, {
"tag": "tag2",
"description": "description2"
}, {
"tag": "tag3",
"description": "description3"
}]
The element to be removed is known {"tag":"tag2", "description":"description2"}.
How can i find this element and remove from the array.
Please find the code which i am using to remove an element
var actionDic = [];
actionDic.push({
description: desc,
tag: tag
});
The actionDic array is populated as user enter text in textinput and selects 'add' option.
var deleterow = {
tag: "tag2",
description: "description2"
};
var index = $.inArray(deleterow, actionDic);
if (index != -1) {
actionDic.splice(index, 1);
}
The correct index is not obtained. Kindly let me know what wrong is in the code.
Thanks.
Since the comparison between the item to remove and each element in actionDic isn't trivial, you could use jQuery.grep():
actionDic = jQuery.grep(actionDic, function(elem) {
return elem.tag == deleterow.tag && elem.description == deleterow.description;
}, true);
Demo
It performs a search using a custom search function and returns the array elements that didn't match. The result replaces the previous value of actionDic and effectively removed that one item.
Update
Unfortunately, this method could be considered heavy because a new array gets created at each invocation; both in terms of what jQuery can do and standard JavaScript functionality, this particular feature is lacking. ECMAScript 6 will have the Array.prototype.find() method that will do the job of finding the index in an array (with which you can perform the splice).
You can of course write one yourself too:
(function($) {
$.find = function(arr, fn) {
for (var i = 0, len = arr.length; i < len; ++i) {
if (fn(arr[i], i, arr)) {
return i;
}
}
return -1;
};
}(jQuery));
var index = $.find(actionDic, function(elem) {
return elem.tag == deleterow.tag && elem.description == deleterow.description;
});
if (index != -1) {
actionDic.splice(index, 1);
}
Demo
I've implemented an indexOfObject method of Array prototype in one of my projects. It searches for an index of object by the given property name and value. Here it is:
Array.prototype.indexOfObject = function(searchTerm, property) {
for (var i = 0, l = this.length; i < l; i++) {
if (this[i][property] === searchTerm) {
return i;
}
}
return -1;
};
You can use this method to find the index of your object using a unique property. In your case you can use it like this:
var arr = [{
"tag": "tag1",
"description": "description1"
}, {
"tag": "tag2",
"description": "description2"
}, {
"tag": "tag3",
"description": "description3"
}], deleteObject = {
tag: "tag2",
description: "description2"
};
var index = arr.indexOfObject(deleteObject.tag, 'tag');
Then you can use that index to remove the object from the array:
if (index > -1) {
arr.splice(index, 1);
}
Here is the working example in JSFiddle.
var actionDic = [{
"tag": "tag1",
"description": "description1"
}, {
"tag": "tag2",
"description": "description2"
}, {
"tag": "tag3",
"description": "description3"
}]
var element = {"tag":"tag2", "description":"description2"}
for(var i=0;i<actionDic.length;i++) {
var found = false;
for(each in actionDic[i]) {
if(actionDic[i][each] == element[each]) {
found = true
} else {
found = false;
break;
}
}
if(found) {
actionDic.splice(i,1);
found=false;
}
}
This gets your inner array objects:
for (var x = 0; x < actionDic.length; x++){
var arrayItem = actionDic[x];
if (arrayItem["tag"] == "tag2"){
alert(arrayItem["description"]);
}
}
Working fiddle: http://jsfiddle.net/4khjp/
You can use underscore.js which contains many useful helpers for Objects, Arrays etc.
Removing array element:
array = _.reject(array, function(item) {
return item.tag == 'tag2'; // <- if tag is unique to whole array
});

Javascript: Getting all existing keys in a JSON array

I have a JSON array like below:
var jsonArray = [{"k1":"v1"},{"k2":"v2"},{"k3":"v3"},{"k4":"v4"},{"k5":"v5"}]
I don't know which keys does exists in this array.
I want to get all the existing key from the array.
It should be possible something like this:
for(i=0;i<jsonArray.lenght;i++){
// something like- key = jsonArray[i].key
// alert(key);
}
Please tell me the method or way to get all keys existing in Json array.
Regards
Why don't you use a
var jsonObject = {"k1":"v1","k2":"v2","k3":"v3","k4":"v4","k5":"v5"}
instead of your
var jsonArray = [{"k1":"v1"},{"k2":"v2"},{"k3":"v3"},{"k4":"v4"},{"k5":"v5"}]
? Then the solution would be so simple: Object.keys(jsonObject).
Try this:
var L = jsonArray.length;
for (var i = 0; i < L; i++) {
var obj = jsonArray[i];
for (var j in obj) {
alert(j);
}
}
I've also made some modifications of your current code (like length caching).
Loop through the object properties, and select the first "real" one (which given your data schema should be the only real one).
var jsonArray = [{"k1":"v1"},{"k2":"v2"},{"k3":"v3"},{"k4":"v4"},{"k5":"v5"}]
for (var i = 0; i < jsonArray.length; i++) {
for (var prop in jsonArray[i]) {
if (jsonArray[i].hasOwnProperty(prop)) {
var key = prop;
break;
}
}
alert(key);
}
See How to loop through items in a js object? for an explanation of why it's important to use hasOwnProperty here.
Try this:
jsonArray.reduce(function(keys, element){
for (key in element) {
keys.push(key);
}
return keys;
},[]);
This should also work for multiple keys in the array objects.
If you're supporting old browsers that don't have reduce and map, then consider using a shim.
var id = { "object": "page", "entry": [{ "id": "1588811284674233", "time": 1511177084837, "messaging": [{ "sender": { "id": "1393377930761248" }, "recipient": { "id": "1588811284674233" }, "timestamp": 1511177084553, "message": { "mid": "mid.$cAAX_9pLcfu1mCnGmiVf2Sxd2erI2", "seq": 1882, "text": "a" } }] }] };
function getKey(obj, data) {
//#author dvdieukhtn#gmail.com
var data = data || [];
if (obj) {
var keys = Object.keys(obj);
for (var pos in keys) {
console.log();
data.push(keys[pos]);
if ((obj[keys[pos]].constructor === Array)) {
for (var i = 0; i < obj[keys[pos]].length; i++) {
getKey(obj[keys[pos]][i], data);
}
}
else if (obj[keys[pos]].constructor === Object) {
getKey(obj[keys[pos]], data);
}
}
return data;
}
}
console.log(getKey(id));

loop a JS Object

I have this JS object:
{
"data": {
"nid": [{
"cid": "32",
"uid": "780",
"comment": "text"
}]
},
"request_status": "found"
}
how can I loop through these items to get comment value ("comment":"text")?
You don't really need to loop to get it. Just do...
var obj = {"data":{"nid":[{"cid":"32","uid":"780","comment":"text"}]},"request_status":"found"};
var text = obj.data.nid[0].comment;
Or if there are several, you can use forEach...
obj.data.nid.forEach(function(val,i) {
alert( val.comment );
});
Or a traditional for loop...
for( var i = 0; i < obj.data.nid.length; i++ ) {
alert( obj.data.nid[i].comment );
}
Or if you want to build an Array, use map...
var arr = obj.data.nid.map(function(val,i) {
return val.comment;
});
Or again a traditional for loop...
var arr = []
for( var i = 0; i < obj.data.nid.length; i++ ) {
arr.push( obj.data.nid[i].comment );
}
Given:
var obj = {
"data": {
"nid": [{
"cid": "32",
"uid": "780",
"comment": "text"
}]
},
"request_status": "found"
};
The direct way to retrieve the comment is:
obj["data"]["nid"][0]["comment"]
// or
obj.data.nid[0].comment
As far as "looping" through the items to get the value, I'm not sure how a loop makes sense. Are you saying you might not know the structure of the object but you know it will have a "comment" field in there somewhere?
The "nid" array only has one item in it - if this was just a sample but really you'll have an array with more values you can loop through that array:
var nid = obj["data"]["nid"], // get a direct reference to the array to save
i; // repeating obj.data.nid everywhere
for (i=0; i < nid.length; i++) {
// do something with the comment in the current item
console.log(nid[i]["comment"]);
}
If you're just referring to that specific object (or if every object you are working with follows that same pattern), then you can just access the value directly:
var theObj = {"data":{"nid":[{"cid":"32","uid":"780","comment":"text"}]},"request_status":"found"};
alert(theObj.data.nid[0].comment);
If you want to do something iterative, then perhaps try this:
var theObj = {"data":{"nid":[{"cid":"32","uid":"780","comment":"text"}]},"request_status":"found"};
for (var index = 0; index < theObj.data.nid.length; index++) {
var item = theObj.data.nid[index];
if (item.comment) {
alert(item.comment);
}
}
Or if you really want to do the entire thing iteratively:
window.searchObj = function(theObj) {
if (theObj.comment) {
alert(theObj.comment);
}
if (theObj instanceof Array) {
searchArray (theObj);
}
else if (theObj instanceof Object) {
for (var key in theObj) {
searchObj(theObj[key]);
}
}
};
window.searchArray = function(theArray) {
for (var index = 0; index < theArray.length; index++) {
var item = theArray[index];
searchObj(item);
}
};
var theObj = {"data":{"nid":[{"cid":"32","uid":"780","comment":"text"}]},"request_status":"found"};
searchObj(theObj);

Categories

Resources