1

I have an array of objects and I need to add a key to the object as well as the parent array/object with the same values.

Below is my implementation:

const data = [{
    "label": "Grand Parent 1",
    "index": 0,
    "code": "GRAND_PARENT_1",
    "defaultCollapsed": true,
    "items": [{
        "id": 1,
        "items": [{
            "id": 100,
            "label": "Child 1",
            "url": "#CHILD_1",
            "code": "CHILD_1"
          },
          {
            "id": 200,
            "label": "Child 2",
            "url": "#CHILD_2",
            "code": "CHILD_2"
          },
          {
            "id": 300,
            "label": "Child 3",
            "url": "#CHILD_3",
            "code": "CHILD_3"
          },
          {
            "id": 400,
            "label": "Child 4",
            "url": "#CHILD_4",
            "code": "CHILD_4"
          }
        ],
        "defaultCollapsed": false,
        "label": "Parent 1"
      },
      {
        "id": 2,
        "items": [],
        "defaultCollapsed": true,
        "label": "Parent 2"
      },
      {
        "id": 3,
        "items": [],
        "defaultCollapsed": true,
        "label": "Parent 3"
      },
      {
        "id": 4,
        "items": [],
        "defaultCollapsed": true,
        "label": "Parent 4"
      }
    ]
  },
  {
    "label": "Grand Parent 2",
    "index": 1,
    "code": "GRAND_PARENT_2",
    "defaultCollapsed": true,
    "items": []
  },
  {
    "label": "Grand Parent 3",
    "index": 2,
    "code": "GRAND_PARENT_3",
    "defaultCollapsed": true,
    "items": []
  }
]

const filterData = (data, value) => {
  const r = _.filter(data, item => {
    const dataMap = _.map(item.items, subItem => {
        const subItemMap = _.map(subItem.items, subsecItem => {
          if(subsecItem.code === value) {
          return item
          }
        })
    })
  })
  return r;
}

console.log(filterData(data, 'CHILD_1'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>

So I want to add a key called selected: true to the child as well as the parent objects when the value to the function is CHILD_1;

Expected Output:

[
  {
    "label": "Grand Parent 1",
    "index": 0,
    "code": "GRAND_PARENT_1",
    "defaultCollapsed": true,
    "selected": true,
    "items": [
      {
        "id": 1,
        "items": [
          {
            "id": 100,
            "label": "Child 1",
            "url": "#CHILD_1",
            "code": "CHILD_1",
            "selected": true
          },
          {
            "id": 200,
            "label": "Child 2",
            "url": "#CHILD_2",
            "code": "CHILD_2"
          },
          {
            "id": 300,
            "label": "Child 3",
            "url": "#CHILD_3",
            "code": "CHILD_3"
          },
          {
            "id": 400,
            "label": "Child 4",
            "url": "#CHILD_4",
            "code": "CHILD_4"
          }
        ],
        "defaultCollapsed": false,
        "label": "Parent 1",
        "selected": true
      },
      {
        "id": 2,
        "items": [],
        "defaultCollapsed": true,
        "label": "Parent 2"
      },
      {
        "id": 3,
        "items": [],
        "defaultCollapsed": true,
        "label": "Parent 3"
      },
      {
        "id": 4,
        "items": [],
        "defaultCollapsed": true,
        "label": "Parent 4"
      }
    ]
  },
  {
    "label": "Grand Parent 2",
    "index": 1,
    "code": "GRAND_PARENT_2",
    "defaultCollapsed": true,
    "items": []
  },
  {
    "label": "Grand Parent 3",
    "index": 2,
    "code": "GRAND_PARENT_3",
    "defaultCollapsed": true,
    "items": []
  }
]

Please advice. I was stuck at trying to filter the data based on the value.

2 Answers 2

1

You could take a function which returns true or false depending on the find of the wanted key/value for updating the object with another property.

This approach works for arbitrary depth of data.

function update(array, key, value, object) {
    var found = false;
    array.forEach(o => {
        if (o[key] === value || update(o.items || [], key, value, object)) {
            found = true;
            Object.assign(o, object);
        }
    });
    return found;
}

var data = [{ label: "Grand Parent 1", index: 0, code: "GRAND_PARENT_1", defaultCollapsed: true, items: [{ id: 1, items: [{ id: 100, label: "Child 1", url: "#CHILD_1", code: "CHILD_1" }, { id: 200, label: "Child 2", url: "#CHILD_2", code: "CHILD_2" }, { id: 300, label: "Child 3", url: "#CHILD_3", code: "CHILD_3" }, { id: 400, label: "Child 4", url: "#CHILD_4", code: "CHILD_4" }], defaultCollapsed: false, label: "Parent 1" }, { id: 2, items: [], defaultCollapsed: true, label: "Parent 2" }, { id: 3, items: [], defaultCollapsed: true, label: "Parent 3" }, { id: 4, items: [], defaultCollapsed: true, label: "Parent 4" }] }, { label: "Grand Parent 2", index: 1, code: "GRAND_PARENT_2", defaultCollapsed: true, items: [] }, { label: "Grand Parent 3", index: 2, code: "GRAND_PARENT_3", defaultCollapsed: true, items: [] }];

update(data, 'code', 'CHILD_1', { selected: true });

console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }

If you like to set all not found items to false, you could take an depth first approach and visit all elements.

function update(array, value) {
    var found = false;
    array.forEach(o => {
        o.selected = update(o.items || [], value) || o.code === value;
        found = found || o.selected;
    });
    return found;
}

var data = [{ label: "Grand Parent 1", index: 0, code: "GRAND_PARENT_1", defaultCollapsed: true, items: [{ id: 1, items: [{ id: 100, label: "Child 1", url: "#CHILD_1", code: "CHILD_1" }, { id: 200, label: "Child 2", url: "#CHILD_2", code: "CHILD_2" }, { id: 300, label: "Child 3", url: "#CHILD_3", code: "CHILD_3" }, { id: 400, label: "Child 4", url: "#CHILD_4", code: "CHILD_4" }], defaultCollapsed: false, label: "Parent 1" }, { id: 2, items: [], defaultCollapsed: true, label: "Parent 2" }, { id: 3, items: [], defaultCollapsed: true, label: "Parent 3" }, { id: 4, items: [], defaultCollapsed: true, label: "Parent 4" }] }, { label: "Grand Parent 2", index: 1, code: "GRAND_PARENT_2", defaultCollapsed: true, items: [] }, { label: "Grand Parent 3", index: 2, code: "GRAND_PARENT_3", defaultCollapsed: true, items: [] }];

update(data, 'CHILD_1');

console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Sign up to request clarification or add additional context in comments.

6 Comments

please see the result above. any parent get selected: true.
Yeah, since selected was in the last, I didnt pay a look. Thanks for your help.
How do I reset the previous selected child to false when the code is CHILD_2? I want CHILD_1 to be set selected: false when code is CHILD_2?
is it always the same key to search for and do you want to set all previously selected property to false, if they are not in the direct line to root? if so, you need to set all object to false in or true, depending on found.
how do I reset selected to false if the current child is already true?
|
1

This solution is a bit more efficient because it stops as soon as the given child is found. forEach() doesn't allow you to do that. I also find it a bit more clear to read.

function select(items, value) {
  if (!Array.isArray(items)) {
    return false;
  }

  for (const item of items) {
    if (item.code === value || select(item.items, key, value)) {
      item.selected = true;
      return true;
    }
  }

  return false;
}

select(data, "CHILD_1");

select() will return true if the child was found and false otherwise.

In case you need to unselect something that was previously selected:

function reset(items) {
  if (!Array.isArray(items)) {
    return;
  }

  for (const item of items) {
    if (item.selected) {
      reset(item.items);
      delete item.selected;
      break;
    }
  }
}

reset(data);

This approach is as smart as the selection, since it stops as soon as it finds the selected element.

To execute both:

function resetAndSelect(data, value) {
  reset(data);
  select(data, value);
}

6 Comments

How do I reset the previous selected child to false when the code is CHILD_2? I want CHILD_1 to be set selected: false when code is CHILD_2?
@a2441918 added a function to unselect anything previously selected and reset the array to its initial state.
Can we do it in a same function?
This is as efficient as you can get with the information you provided. You probably don't need to mark elements as selected at all; it looks like you could store that information in a separate data structure... but then you'd need to provide extra info. The code I suggested here is efficient, easy to read and maintain... but one can probably do better if the whole picture is provided. We are doing O(n) here, but we can surely do O(1).
Sure Lucio. What I am trying to achieve is design.cms.gov/components/vertical-nav. onClick of child, I need to set true for parent as well as grandparent so that they show in blue color, and when I click the other child, the previous child selected becomes false but still the parent and grandparent are selected as the new child is still under the same parent.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.