Set dynamic loaded children as checked

1 Answer 27 Views
TreeView
Bernd
Top achievements
Rank 5
Bronze
Bronze
Bronze
Bernd asked on 08 Jul 2024, 03:18 PM

Hi.

I use the TreeView to display my data and I have added some logic to load the "branches" of the tree dynamically every time the user clicks on the little triangle. That works perfectly. But now I have an addition to solve and I'm a little stuck regarding the "checked" state.

I use the checkboxes infront of the branches to make the branches checkable and I use the onCheckChange event to trigger the handleTreeViewCheckChange function with the checkChildren and checkParents Options both set to true. So far so good. But if I check a parent before I dynamically load the children, the children do not get checked automatically too. I have added to screenshots so you see the status before and after loading the children so you can better see what I mean. So before clicking on the little triangle infront of "Frankfurt" I check the checkbox for Frankfurt. Then I click the triangle to expand Frankfurt and I would like to achieve the children of Frankfurt are checked too after dynamically loading them.

I thought aoubt running the handleTreeViewCheckChange function again after the loading of the children is done, but of course then the TreeViewExpandChangeEvent is not present anymore, so I can't run it. Also I tried to manually create a new itemHierarchicalIndex and add it to the check state but somehow that did not work either. For your reference I have also attached my script.


/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/no-explicit-any */
import * as React from 'react';
import { ENVIRONMENT, LOCATION } from '@iconag/general-types';
// import { LocationSelectionContext } from '@iconag/context';
import { GLOBALS } from '@iconag/globals';
import {
  handleTreeViewCheckChange,
  processTreeViewItems,
  TreeView,
  TreeViewCheckDescriptor,
  TreeViewExpandChangeEvent,
} from '@progress/kendo-react-treeview';
import {
  parseLocationChildren,
  parseLocationData,
  useReadLocationChildren,
  useReadSingleLocationHandler,
} from '@iconag/dataHandlers';
import { convertLocationDataToTreeData } from '@iconag/scripts';
import { mapTree } from '@progress/kendo-react-common';
import Loading from '../../Loading/Loading';

const textField = 'text';

type CheckedLocationIds = { id: string; itemHierarchicalIndex: string };

type LocalizationTreeSelectorProps = {
  environment: ENVIRONMENT.environment;
  locationURL: string;
};

const LocalizationTreeSelector = (props: LocalizationTreeSelectorProps) => {
  const { environment, locationURL } = props;
  const { LOCATION_ROOT } = GLOBALS;
  const [expanded, setExpanded] = React.useState<string[]>([]);
  const [check, setCheck] = React.useState<
    | any[]
    | (TreeViewCheckDescriptor & {
        ids: any[];
      })
  >({
    ids: [],
    applyCheckIndeterminate: true,
  });
  // const { selectedLocations, setSelectedLocations } = React.useContext(
  //   LocationSelectionContext
  // );
  const [treeData, setTreeData] = React.useState<
    LOCATION.LocationTreeDataItem[]
  >([]);

  const { readSingleLocation, readSingleLocationService, locationData } =
    useReadSingleLocationHandler(parseLocationData);
  const { readLocationChildren, locationChildren } = useReadLocationChildren(
    parseLocationChildren
  );
  const [currentParent, setCurrentParent] = React.useState<string | null>(null);
  const [checkedLocations, setCheckedLocations] = React.useState<
    CheckedLocationIds[]
  >([]);
  const copyDataAndLocation = (locationIndex: string) => {
    const data = treeData.slice();
    let location: any;
    mapTree(data, 'items', (item) => {
      if (item.id === locationIndex) location = item;
    });
    return { data, location };
  };

  React.useEffect(() => {
    if (readSingleLocationService.status === 'init' && locationData === null) {
      readSingleLocation(environment, 'Region', LOCATION_ROOT, locationURL);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  React.useEffect(() => {
    if (locationData !== null) {
      const treeItem: LOCATION.LocationTreeDataItem =
        convertLocationDataToTreeData(locationData, true);
      if (treeData.length === 0) {
        setTreeData([treeItem]);
      }
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [locationData]);

  React.useEffect(() => {
    if (locationChildren !== null && currentParent !== null) {
      const { data, location } = copyDataAndLocation(currentParent);
      location.items = locationChildren.map((childItem, idx) => {
        const parentLocationIsChecked = checkedLocations.find(
          (loc) => loc.id === location.id
        );
        if (parentLocationIsChecked) {
          const newId =
            parentLocationIsChecked.itemHierarchicalIndex + '_' + idx;
        }
        return convertLocationDataToTreeData(childItem, false);
      });
      setTreeData(data);
      setCurrentParent(null);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [locationChildren]);

  const onExpandChange = (event: TreeViewExpandChangeEvent) => {
    const parentId = event.item.id;
    setCurrentParent(parentId);
    const expand = expanded.slice();
    const index = expand.indexOf(event.itemHierarchicalIndex);
    if (index === -1) {
      expand.push(event.itemHierarchicalIndex);
    } else {
      expand.splice(index, 1);
    }
    setExpanded(expand);
    if (event.item.hasChildren && !event.item.items) {
      readLocationChildren(
        event.item.type,
        event.item.id,
        environment,
        locationURL
      );
    }
  };

  const onCheckChange = (event: TreeViewExpandChangeEvent) => {
    const newCheckedData = handleTreeViewCheckChange(event, check, treeData, {
      checkChildren: true,
      checkParents: true,
      singleMode: false,
    });
    if (checkedLocations.find((locIds) => locIds.id === event.item.id)) {
      setCheckedLocations((checkedLocations) =>
        checkedLocations.filter((loc) => loc.id !== event.item.id)
      );
    } else {
      const newCheckedLocation: CheckedLocationIds = {
        id: event.item.id,
        itemHierarchicalIndex: event.itemHierarchicalIndex,
      };
      setCheckedLocations((checkedLocations) => [
        ...checkedLocations,
        newCheckedLocation,
      ]);
    }
    setCheck(newCheckedData);
  };

  if (readSingleLocationService.status === 'loading') {
    return <Loading spinnerSize="medium" />;
  }

  return (
    <TreeView
      data={processTreeViewItems(treeData, { expand: expanded, check: check })}
      onExpandChange={onExpandChange}
      onCheckChange={onCheckChange}
      expandIcons={true}
      textField={textField}
      checkboxes={true}
      animate={false}
    />
  );
};
export default LocalizationTreeSelector;

The useEffect in line 89 is where the children have been loaded and where I then tried to manipulate the check state. I removed my trials though because they did not work at all.

Any help would be greatly appreciated.

Thanks,

Bernd 

1 Answer, 1 is accepted

Sort by
0
Konstantin Dikov
Telerik team
answered on 10 Jul 2024, 01:30 PM

Hi Bernd,

Thank you for reaching out to us.

I have prepared a simple example demonstrating how to add new item ids` to the selected state:

The example is very simplified, just to demonstrate that adding items dynamically will still check/uncheck them if their ids' are included in the checked state.

If further assistance is needed, do not hesitate to contact us again.

 

Regards,
Konstantin Dikov
Progress Telerik

Stay tuned by visiting our public roadmap and feedback portal pages! Or perhaps, if you are new to our Kendo family, check out our getting started resources!

Bernd
Top achievements
Rank 5
Bronze
Bronze
Bronze
commented on 11 Jul 2024, 07:11 AM | edited

Hi.

Thanks for your answer. That is understandable and works for me. But now I have another question. I have forked your example and added an id to the sub-items and a button. When the button is clicked I'd like to uncheck the "Sofas" item. The issue is that I only know the id and not the itemHierarchicalIndex, so I could uncheck it using the itemHierarchicalIndex and I also don't have the TreeViewCheckChangeEvent in the onRemoveItem handler. How would I do that?

Thanks for your help!

Greetings,

Bernd

Konstantin Dikov
Telerik team
commented on 15 Jul 2024, 05:49 AM

Hi Bernd,

You can create a function that will find the hierarchical index of an item within the tree data by using either the "id" or the "text" of the nodes. Here is the modified example using such function and the "id" of an item:

Bernd
Top achievements
Rank 5
Bronze
Bronze
Bronze
commented on 15 Jul 2024, 07:51 AM

Hi.

Exactly what I needed. Thank you very much!

Greetings,

Bernd

Tags
TreeView
Asked by
Bernd
Top achievements
Rank 5
Bronze
Bronze
Bronze
Answers by
Konstantin Dikov
Telerik team
Share this question
or