How to add a child node in a nested object at a particular position using javascript

Viewed 227

I have a data structure as given below

ds: [
          { id: "0a12", pos: 0, name: "PH1" },
          {
            id: "8f83",
            name: "PH2",
            pos: 1,
            children: [
              { id: "ll54", pos: 0, name: "L1" },
              {
                id: "97cs",
                name: "L2",
                pos: 1,
                children: [
                  { id: "80s3", pos: 0, name: "LL1" },
                  { id: "2dh3", pos: 1, name: "LL2" },
                ],
              },
            ],
          },
          { id: "75fd", pos: 2, name: "PH3" },
          { id: "34jg", pos: 3, name: "PH4" },
        ],

I am creating a org chart wherein I have option to add a node to the left or right. On clicking the node, I can get the node obj as an argument (ie) if I clicked on 'LL1' I can get that object, along with second arg called as type which will contain either 'left' or 'right'.

so now the type == 'left' then my resultant data structure should be like

ds: [
          { id: "0a12", pos: 0, name: "PH1" },
          {
            id: "8f83",
            name: "PH2",
            pos: 1,
            children: [
              { id: "4", pos: 0, name: "L1" },
              {
                id: "5",
                name: "L2",
                pos: 1,
                children: [
                  { id: "36fd", pos: 0, name: "" }, // new node for type === 'left'
                  { id: "80s3", pos: 1, name: "LL1" },
                  { id: "2dh3", pos: 2, name: "LL2" },
                ],
              },
            ],
          },
          { id: "75fd", pos: 2, name: "PH3" },
          { id: "34jg", pos: 3, name: "PH4" },
        ],

and if type === 'right'

ds: [
          { id: "0a12", pos: 0, name: "PH1" },
          {
            id: "8f83",
            name: "PH2",
            pos: 1,
            children: [
              { id: "4", pos: 0, name: "L1" },
              {
                id: "5",
                name: "L2",
                pos: 1,
                children: [
                  { id: "80s3", pos: 0, name: "LL1" },
                  { id: "36fd", pos: 1, name: "" }, // new node for type === 'right'
                  { id: "2dh3", pos: 2, name: "LL2" },
                ],
              },
            ],
          },
          { id: "75fd", pos: 2, name: "PH3" },
          { id: "34jg", pos: 3, name: "PH4" },
        ],

Note: for those nodes that don't have children we shouldn't initialize an empty array to children attribute

2 Answers

You can use Array.slice and spread operator to insert the new node into your array.

let data = [
  { id: '0a12', pos: 0, name: 'PH1' },
  {
    id: "8f83",
    name: "PH2",
    pos: 1,
    children: [
      { id: '4', pos: 0, name: 'L1' },
      {
        id: '5',
        name: 'L2',
        pos: 1,
        children: [
           { id: '80s3', pos: 0, name: 'LL1' },
           { id: '2dh3', pos: 1, name: 'LL2' },
        ],
      },
   ],
  },
  { id: '75fd', pos: 2, name: 'PH3' },
  { id: '34jg', pos: 3, name: 'PH4' },
];

function addNode(direction, idElement, ptr) {
  const elementIndex = ptr.findIndex(x => x.id === idElement);

  if (elementIndex === -1) {
    // Go deeper
    return ptr.map(x => x.children ? {
       ...x,
       children: addNode(direction, idElement, x.children),
    }: x);
  }
  
  const offset = direction === 'left' ? 0 : 1;
  
  // Insert the new node in the correct position
  const mutatedPtr = [
     ...ptr.slice(0, elementIndex + offset),
      
     { id: 'XXXX', pos: elementIndex + offset, name: '' },
      
     ...ptr.slice(elementIndex + offset),
  ];
  
  // change the positions
  mutatedPtr.forEach((x, xi) => {
    x.pos = xi;
  });
  
  return mutatedPtr;
}

data = addNode('right', '75fd', data);
data = addNode('left', '75fd', data);
data = addNode('left', '2dh3', data);
data = addNode('right', '2dh3', data);

console.log(data);

This is a tree insertion problem. First you need to find the index to the anchor node and then insert a new node at index if type is "left" or index + 1 if type is "right". Here's a solution that works for your case. Note that you need to set the right "id" using whatever id generator you have.

ds = [
  { id: "0a12", pos: 0, name: "PH1" },
  {
    id: "8f83",
    name: "PH2",
    pos: 1,
    children: [
      { id: "4", pos: 0, name: "L1" },
      {
        id: "5",
        name: "L2",
        pos: 1,
        children: [
          { id: "80s3", pos: 0, name: "LL1" },
          { id: "2dh3", pos: 1, name: "LL2" }
        ]
      }
    ]
  },
  { id: "75fd", pos: 2, name: "PH3" },
  { id: "34jg", pos: 3, name: "PH4" }
];

// Returns parent & position
function findNode(childNodes, name) {
  for (var ii = 0; ii < childNodes.length; ii++) {
    let child = childNodes[ii];
    if (child.name == name) {
      return {
        childNodes: childNodes,
        index: ii
      };
    }
    if (child.children) {
      let result = findNode(child.children, name);
      if (result) {
        return result;
      }
    }
  }
  return null;
}

function rewritePositions(childNodes) {
  for (var ii = 0; ii < childNodes.length; ii++) {
    childNodes[ii].pos = ii;
  }
}

function insertNode(name, type) {
  let searchResult = findNode(ds, name);
  if (!searchResult) {
    return;
  }

  let index = searchResult.index;
  let newPosition = type === "left" ? index : index + 1;
  let newNode = { id: "todo: index", pos: newPosition, name: "" };
  searchResult.childNodes.splice(newPosition, 0, newNode);
  rewritePositions(searchResult.childNodes);
}
Related