8

suppose I have this kind of data...

data = [{
    "_id" : "1",
    "parentId" : "thisPostId",
    "topLevelId" : "1",
    "text" : "<p>comment</p>",
},
{
    "_id" : "2",
    "parentId" : "1",
    "topLevelId" : "1",
    "text" : "<p>reply to comment</p>",
},
{
    "_id" : "3",
    "parentId" : "2",
    "topLevelId" : "1",
    "text" : "<p>reply to reply to comment</p>",
},
{
    "_id" : "4",
    "parentId" : "3",
    "topLevelId" : "1",
    "text" : "<p>reply to reply to reply to comment</p>",
}]

I need to remove a comment and all of its child...

if comment to remove is _id:1,, then I need an array of ["1","2","3","4"],,, then i can run Coll.remove({_id:{$in:["1","2","3","4"]}}, callback);

if comment to remove is _id:2,, then I need an array of ["2","3","4"]

if comment to remove is _id:3,, then I need an array of ["3","4"]

if comment to remove is _id:4,, then I need an array of ["4"]

I tried this (with no idea)...

_.forEach(data, function(value, key){
    _.pluck(_.where(key, { "parentId" : "2" }), '_id');
});

and not working...

any help with javascript/lodash/underscore will be appreciated,,,

thank You...

Stefano Saitta
  • 1,844
  • 1
  • 19
  • 34
  • hii @Rajesh thanks for your response,,, the text key only to make better understand,, just ignore... what we should focus is `_id` and `parentId` –  Jan 11 '16 at 11:02
  • This looks like a [XY problem](http://meta.stackexchange.com/a/66378/312734), you seem to want to cascade delete a document: http://stackoverflow.com/questions/14348516/cascade-style-delete-in-mongoose – Shanoor Jan 11 '16 at 11:39
  • hii @ShanShan thank You for your response,,, i'm doing project with meteor,, it seem isn't work since meteor have own mongo driver called minimongo,,, but thank You,, didn't know before that mongoose have such feature –  Jan 11 '16 at 11:51
  • meteorjs has hooks to achieve the same sort of feature. You should post a question about the specific issue you have, the meteor users should be able to help. – Shanoor Jan 11 '16 at 12:00

8 Answers8

3

Here is another interpretation using the native Array.prototype.reduce method to only add the child elements to the returned array.

edit, didn't read question properly, this will now return the current id and all children.

var data = [{
    "_id" : "1",
    "parentId" : "thisPostId",
    "topLevelId" : "1",
    "text" : "<p>comment</p>",
},
{
    "_id" : "2",
    "parentId" : "1",
    "topLevelId" : "1",
    "text" : "<p>reply to comment</p>",
},
{
    "_id" : "3",
    "parentId" : "2",
    "topLevelId" : "1",
    "text" : "<p>reply to reply to comment</p>",
},
{
    "_id" : "4",
    "parentId" : "3",
    "topLevelId" : "1",
    "text" : "<p>reply to reply to reply to comment</p>",
}];

function getChildIds( arr, id ){
  var parentFound = false;
  return arr.reduce(function( ret, item ){
    if( parentFound === false && item._id == id ){
      parentFound = true;
    } 
    
    if( parentFound ) {
      ret = ret.concat( item._id );
    }
    
    return ret;
  }, []);
}

console.log( getChildIds(data, '1') );
console.log( getChildIds(data, '2') );
console.log( getChildIds(data, '3') );
console.log( getChildIds(data, '4') );
<script src="http://codepen.io/synthet1c/pen/WrQapG.js"></script>

any order, not sure why it's necessary thought.

var data = [{
  "_id": "2",
  "parentId": "1",
  "topLevelId": "1",
  "text": "<p>reply to comment</p>",
}, {
  "_id": "1",
  "parentId": "thisPostId",
  "topLevelId": "1",
  "text": "<p>comment</p>",
}, {
  "_id": "4",
  "parentId": "3",
  "topLevelId": "1",
  "text": "<p>reply to reply to reply to comment</p>",
}, {
  "_id": "3",
  "parentId": "2",
  "topLevelId": "1",
  "text": "<p>reply to reply to comment</p>",
}];

function getChildIdsInAnyOrder(arr, id) {
  return arr.reduce(function(ret, item) {
    if ( parseInt(item._id) >= parseInt(id) ) {
      ret = ret.concat(item._id);
    }
    return ret;
  }, []);
}

console.log(getChildIdsInAnyOrder(data, '1'));
console.log(getChildIdsInAnyOrder(data, '2'));
console.log(getChildIdsInAnyOrder(data, '3'));
console.log(getChildIdsInAnyOrder(data, '4'));
<script src="http://codepen.io/synthet1c/pen/WrQapG.js"></script>
synthet1c
  • 6,152
  • 2
  • 24
  • 39
  • how is it different from @rayon 's answer http://stackoverflow.com/a/34719952/1398867 – Venugopal Jan 11 '16 at 11:28
  • it's less complicated, has less processes, but I couldn't tell you which is more efficient, `[].reduce` uses function each iteration so may be slower. – synthet1c Jan 11 '16 at 11:32
  • If the order of the array changes, this doesn't find all the children of the parent id. – Vodka_Tonic Jan 11 '16 at 11:33
  • what do you mean by that? what are the id's going to be in production, will they be integers, why would the order of the array change. I will add another function but you need to explain your actual problem – synthet1c Jan 11 '16 at 11:34
  • https://jsfiddle.net/LLfkop49/ what if the data isn't sorted? and a child exists in an index before its parent? it will never get deleted – Vodka_Tonic Jan 11 '16 at 11:34
  • then what @Vodka_Tonic is saying is unnecessary. the objects need to be indexed in order. of precedence. – synthet1c Jan 11 '16 at 11:44
  • You're coding for a specific edge case which will be a nightmare to debug if the data ever changed. – Vodka_Tonic Jan 11 '16 at 11:48
  • Can you post an example @Vodka_Tonic. I'm interested to see how you would handle it. And I'm still unsure if the final structure is a tree. – synthet1c Jan 11 '16 at 11:51
  • Sure with your code and your tests but with the data order changed slightly. https://jsfiddle.net/LLfkop49/2/. If the order ever changed the results would not return child nodes correctly. – Vodka_Tonic Jan 11 '16 at 11:55
3

This is a rather lengthy one using recursion,

function getIDs(arr, id) {
arr = arr || data;
var ret = [];
for (var i = 0; i < arr.length; i++) {
    var item = arr[i];
    if (item.parentId == id || item._id == id) {
        if (ret.indexOf(item._id) < 0) {
            ret.push(item._id);
            var newret = []
            for (var x = 0; x < arr.length; x++) {
                if (x != i) newret.push(arr[x]);
            }
            var children = getIDs(newret, item._id);
            if (children.length > 0) {
                for (var j = 0; j < children.length; j++) {
                    if (!(ret.indexOf(children[j]) >= 0)) { ret.push(children[j]); }
                }
            }
        }

    }
}
return ret;

}

It works by getting the id of the desired parent, then getting the ids of its children, and its children's children, it could do this all day ...

Ikechi Michael
  • 489
  • 3
  • 6
  • 1
    This is the best solution imo. It doesn't care what order the data is in. It will traverse the child nodes of child nodes. While the implications of large data sets may be a problem. It looks like the purest solution for smaller data. – Vodka_Tonic Jan 11 '16 at 11:51
1

First you need a function to get the topLevelId from the object with that matches the search id:

function getTLID(searchId) {
  return data.filter(function(el) {
    return el._id === searchId;
  })[0].topLevelId;
}

With reduce: add the _id of each object to the returned array that has that search id and either have the search id or have a parentId greater or equal to the search id, the use map to grab the _ids.

function getIdArray(searchId) {
  var tlid = getTLID(searchId);
  return data.reduce(function (p, c) {
    var matchSearchId = +c.parentId >= +searchId || c._id === searchId;
    if (c.topLevelId === tlid && matchSearchId) p.push(c._id);
    return p;
  }, []).sort();
}

getIdArray('1') // [ "1", "2", "3", "4" ]
getIdArray('2') // [ "2", "3", "4" ]
getIdArray('3') // [ "3", "4" ]
getIdArray('4') // [ "4" ]

DEMO

If you don't like reduce, perhaps using filter and map.

function getIdArray(searchId) {
  var tlid = getTLID(searchId);
  return data.filter(function(el) {
    var matchSearchId = +el.parentId >= +searchId || el._id === searchId;
    return el.topLevelId === tlid && matchSearchId;
  }).map(function(el) {
    return el._id;
  }).sort();
}

DEMO

Andy
  • 61,948
  • 13
  • 68
  • 95
0

First of all, you need to get the index of the item having mentioned _id, If item exists in the array then you can use array.splice to remove the n elements from mentioned index. To get items from the deleted node, deepcopy of the array is stored in temperory variable.

The splice() method changes the content of an array by removing existing elements and/or adding new elements.

You can calculate the delete count using data.length - index

var data = [{
  "_id": "1",
  "parentId": "thisPostId",
  "topLevelId": "1",
  "text": "<p>comment</p>",
}, {
  "_id": "2",
  "parentId": "1",
  "topLevelId": "1",
  "text": "<p>reply to comment</p>",
}, {
  "_id": "3",
  "parentId": "2",
  "topLevelId": "1",
  "text": "<p>reply to reply to comment</p>",
}, {
  "_id": "4",
  "parentId": "3",
  "topLevelId": "1",
  "text": "<p>reply to reply to reply to comment</p>",
}];
var getIndex = function(_id) {
  for (var i = 0; i < data.length; i++) {
    if (data[i]._id == _id) {
      return i;
    }
  }
};

function deepCopy(obj) {
 if (null == obj || "object" != typeof obj) return obj;
  var copy = obj.constructor();
  for (var attr in obj) {
    if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
  }
  return copy;
}
var _id = 1;

var index = getIndex(_id);
var _temp = deepCopy(data);
var removedData = data.splice(index, 1);
alert(removedData);
if (typeof index !== 'undefined') {
  var neededData = _temp.splice(index, (_temp.length - index));
  alert(neededData);
}

Fiddle here

Community
  • 1
  • 1
Rayon
  • 36,219
  • 4
  • 49
  • 76
0

TRY THIS:

HTML:

<input type="text" id="Txt" />

<button type="button" onclick="check();">
Check
</button>

JS:

data = [{
    "_id" : "1",
    "parentId" : "thisPostId",
    "topLevelId" : "1",
    "text" : "<p>comment</p>",
},
{
    "_id" : "2",
    "parentId" : "1",
    "topLevelId" : "1",
    "text" : "<p>reply to comment</p>",
},
{
    "_id" : "3",
    "parentId" : "2",
    "topLevelId" : "1",
    "text" : "<p>reply to reply to comment</p>",
},
{
    "_id" : "4",
    "parentId" : "3",
    "topLevelId" : "1",
    "text" : "<p>reply to reply to reply to comment</p>",
}];


function check() {
    getIds(document.getElementById("Txt").value);
}

function getIds(id) {
    var allow = false,
        result = [];

    for (var i = 0; i < data.length; i++) {
        if (data[i]._id == id) {
            allow = true;
        }
        if (allow) {
            result.push(data[i]._id)
        }
    }

    retrun result;
}
Andreas
  • 21,535
  • 7
  • 47
  • 56
Suganth G
  • 5,136
  • 3
  • 25
  • 44
0

You can try something like this:

Code

JSFiddle

var data = [{
  "_id": "1",
  "parentId": "thisPostId",
  "topLevelId": "1",
  "text": "<p>comment</p>",
}, {
  "_id": "2",
  "parentId": "1",
  "topLevelId": "1",
  "text": "<p>reply to comment</p>",
}, {
  "_id": "3",
  "parentId": "2",
  "topLevelId": "1",
  "text": "<p>reply to reply to comment</p>",
}, {
  "_id": "4",
  "parentId": "3",
  "topLevelId": "1",
  "text": "<p>reply to reply to reply to comment</p>",
}];

function getDependentList(id) {
  var retList = [];

  data.forEach(function(item) {
    if (item.parentId == id)
      retList.push(item["_id"]);
  });

  if (retList.length > 0) {
    retList.forEach(function(item) {
      retList = retList.concat(getDependentList(item).slice(0));
    });
  }

  return retList;
}

function getRemoveList() {
  var id = document.getElementById("txtInput").value;
  var removeList = [];
  removeList.push(id);
  removeList = removeList.concat(getDependentList(id))
  console.log(removeList);
}
<input type="text" id="txtInput">
<button onclick="getRemoveList()">get Lists</button>
Rajesh
  • 24,354
  • 5
  • 48
  • 79
0

This is a proposal with a temporary object and a recursive call for the ids.

The temporary object o contains all ids and their childrens

{
    "1": ["2"],
    "2": ["3"],
    "3": ["4"],
    "thisPostId": ["1"]
}

After this object is build, the id for the look up is taken and checked if the object contains the property. While all peopertys are arrays, it is possible to iterate over go() and get all id for collecting. If there is another child, the recursive iteration is going on.

var data = [{ "_id": "1", "parentId": "thisPostId", "topLevelId": "1", "text": "<p>comment</p>", }, { "_id": "2", "parentId": "1", "topLevelId": "1", "text": "<p>reply to comment</p>", }, { "_id": "3", "parentId": "2", "topLevelId": "1", "text": "<p>reply to reply to comment</p>", }, { "_id": "4", "parentId": "3", "topLevelId": "1", "text": "<p>reply to reply to reply to comment</p>", }];

function getConnected(s) {
    function go(a) { r.push(a); o[a] && o[a].forEach(go); }

    var o = data.reduce(function (r, a) {
            r[a.parentId] = r[a.parentId] || [];
            r[a.parentId].push(a._id);
            return r;                
        }, {}),
        r = [s];

    o[s] && o[s].forEach(go);
    return r;
}

for (var i = 1; i <= 4; i++) {
    document.write('"' + i + '": ' + JSON.stringify(getConnected(i.toString())) + '<br>');
}
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • heloo @NinaScholz .... care to elaborate a little why you propose using temporary object and a recursive call..?? thank Youu... –  Jan 11 '16 at 18:18
  • hii,, @NinaScholz your code seems efficient when having large data,,, i don't know why though... have comment..?? anyway,, have good reference about temporary object for my further learning..?? thank You so muchh,,, –  Jan 11 '16 at 19:35
  • @KarinaL, it is efficient, because of the hash table. so the look up is very short. other solution presented here, lacks that. – Nina Scholz Jan 11 '16 at 21:40
0

In the OP's comments, you said you're using meteorjs and you seem to want to cascade delete a document. Meteorjs hooks allow this easily:

var idToRemove;
Coll.remove({ _id: idToRemove }, callback);

// what to do after removing a Coll document
Coll.after.remove(function (userId, doc) {
    Coll.remove({ parentId: doc._id });
});

You need to install the collection-hooks package first.

Shanoor
  • 13,344
  • 2
  • 29
  • 40
  • hii @ShanShan thank You for your answer,,, for other purpose i need to get an array from client,,, that's why i want client-side operation,,, but thank Youu,,, –  Jan 11 '16 at 12:17