使用Help-function时需要注意,当你完全采用Kendo UI库推荐的方法时,会遇到一个问题
下面是完全按照Kendo推荐的方法写的
当用户选择1A 和1D
打印check
你会看到ids中包含选中项序列号( 序列号"0_0"对应内容1A, "0_2" 对应内容1D )
如果这个列表的内容一直固定,则没有问题,因为你的序列号和内容始终对应, 如“0_0” 对应1A
但是当列表内容是动态的,则不能这么做,因为序列号和内容不一定匹配
比如,当列表是动态的,这个时候选择第四项,数据的渲染是根据ids的内容来匹配的,
此时选中的ids为("0_0", "0_2", "0_3") 预想选中的内容是1A, 1D, 2B,
但是实际上
你选中的内容是1B, 2A, 2B( 因为新的列表中 "0_0" 对应的是1B, "0_2" 对应的是2A, "0_3"对应的是2B )
结论
当列表内容为固定值,则可以完全按照Kendo推荐的方法去实现,最终选中项从ids中获取
当列表内容是动态的,则最终结果不能仅仅从ids中获取
那么当列表内容是动态的,想获取最终结果怎么做呢?
思路: 充分利用ids, 列表用一个数组表示, 数组中每一项都有一个唯一下标和一个check属性,
如果这项被选中(check为true ),则ids中能找到对应下标,如果没被选中, ids中不存在对应下标。
每次请求数据后,新的列表就会覆盖旧列表, 在覆盖之前根据ids和旧的列表,就可以获取到上一次选中的内容。
举个栗子,因为我做的列表有两层机构, 所以数组结构为
[
{
text: "Machine Name",
expanded: false,
items: [
{text: "HotEye-L12-1"},
{text: "HotForm-L12-2"},
{text: "HotMass-L12-3"},
{text: "HotSystem-L12-3"},
{text: "MCAL-L12-2"},
{text: "Multi_L13"},
{text: "Mx4_L11"}
],
},
]
所以当选中父项时,ids显示为"0" , 选中子项时,
例如,选中HotForm-L12-2,它在数组中的下标为 1, 所以ids为"0_1"
选中MCAL-L12-2,它在数组中的下标为 4, 所以ids为"0_4"
第一次列表有七项,假设选中HotEye-L12-1,MCAL-L12-2, 点击页面其他筛选条件,发送请求,获取到新的machine Name列表
新列表中只有MCAL-L12-2
要求: 显示并选中HotEye-L12-1,MCAL-L12-2
最终结果:
重要分析部分 获取到新列表然后渲染之前,找到旧的ids, 根据ids和旧的列表找到选中项HotEye-L12-1,MCAL-L12-2, 判断新列表中是否存在这两项,然后更新列表和ids.
部分代码
子组件,采用Kendo treeView
FilterTree.tsx
import * as React from "react";
import {
TreeViewCheckChangeEvent,
TreeViewExpandChangeEvent,
TreeView,
processTreeViewItems,
TreeViewOperationDescriptors,
TreeViewCheckChangeSettings,
handleTreeViewCheckChange,
TreeViewCheckDescriptor,
} from "@progress/kendo-react-treeview";
import {IFilterTreeItem} from "./IFilterTreeItem";
export interface IFilterTreeProps<T> {
/** filter list */
filterList: IFilterTreeItem<T>[];
/** used to handle expand/checked property of filter list */
filterOperations: TreeViewOperationDescriptors;
/** triggered this function when user checked or un-checked items */
onCheckChange: (newOperetions: TreeViewOperationDescriptors) => void;
/** triggered this function when user expanded or collapsed items */
onExpandChange: (newOperetions: TreeViewOperationDescriptors) => void;
}
export class FilterTree<T> extends React.Component<IFilterTreeProps<T>> {
constructor(props: IFilterTreeProps<T>) {
super(props);
this.filterUpdate = this.filterUpdate.bind(this);
this.filterExpand = this.filterExpand.bind(this);
}
public render() {
const {filterList, filterOperations} = this.props;
/**
* use kendo component function to handle data:[processTreeViewItems
* ](https://www.telerik.com/kendo-react-ui/components/treeview/api/processTreeViewItems/)
*/
const data = processTreeViewItems(filterList, filterOperations) as IFilterTreeItem<string>[];
return (
<div className="tes-components-mainFilter-filterTree">
<TreeView
className="tes-components-Gallery-filter-kendoUI"
data={data}
checkboxes={true}
expandIcons={true}
onCheckChange={this.filterUpdate}
onExpandChange={this.filterExpand}
/>
</div>
);
}
/**
* toggle when user change filter item
* @param event `TreeViewCheckChangeEvent` user selected item
* * Note: I use kendo component , you can see [Help function
* ](https://www.telerik.com/kendo-react-ui/components/treeview/checkboxes/helper-functions/)
*/
private filterUpdate(event: TreeViewCheckChangeEvent) {
const {filterList, filterOperations} = this.props;
const settings: TreeViewCheckChangeSettings = {checkChildren: true, checkParents: true};
const check = handleTreeViewCheckChange(
event,
filterOperations.check as string[] | TreeViewCheckDescriptor,
filterList,
settings
) as string[] | TreeViewCheckDescriptor;
const newOperetions: TreeViewOperationDescriptors = {
...filterOperations,
check,
};
this.props.onCheckChange(newOperetions);
}
/**
* toggle when user expanded or collapsed filter
* @param event `TreeViewCheckChangeEvent` current filter item
*/
private filterExpand(event: TreeViewExpandChangeEvent) {
const {filterOperations} = this.props;
const expand = (filterOperations.expand as string[]).slice();
const index = expand.indexOf(event.itemHierarchicalIndex);
if (index === -1) {
expand.push(event.itemHierarchicalIndex);
} else {
expand.splice(index, 1);
}
const newOperetions: TreeViewOperationDescriptors = {
...filterOperations,
expand,
};
this.props.onExpandChange(newOperetions);
}
}
IFilterTreeItem.ts
export interface IFilterTreeItem<T> {
/** Displayed label associated with the filterItem */
text: T;
/**
* expanded or collapsed items.
*/
expanded?: boolean;
/** children of the item */
items?: IFilterTreeItem<T>[];
}
父组件
state
/** filter list for machine name */
machineNameFilterTree: IFilterTreeItem<string>[];
/** used to handle expand/checked property of machineName filter list
* * more detail you can see [kendo help function
* ](https://www.telerik.com/kendo-react-ui/components/treeview/checkboxes/helper-functions/)
*/
machineNameOperations: TreeViewOperationDescriptors;
constructor(props) {
super(props);
this.state = {
machineNameFilterTree: [],
machineNameOperations: {
expand: [],
check: {ids: [], applyCheckIndeterminate: true},
},
}
}
componentDidmount
public componentDidMount() {
this.state.logger.debug("componentDidMount start");
//filters.machineNameFilters就是后端传回的新列表
const {machineNameFilterTree, machineNameOperations} = this.changeFormatOfMachineName(filters.machineNameFilters);
this.setState({
machineNameFilterTree,
machineNameOperations,
});
this.state.logger.debug("componentDidMount end");
}
componentDidUpdate
public componentDidUpdate(prevProps: IMainFilterProps) {
this.state.logger.debug("componentDidUpdate start");
//filters是后端传回的列表(请根据你自己的代码来修改此值)
const newFilterList = this.props.filters;
if (JSON.stringify(prevProps.filters) !== JSON.stringify(newFilterList)) {
const {machineNameFilterTree, machineNameOperations} = this.changeFormatOfMachineName(
newFilterList.machineNameFilters
);
this.setState({
machineNameFilterTree,
machineNameOperations,
});
}
this.state.logger.debug("componentDidUpdate end");
}
渲染子组件
/** render machine name filter */
private machineNameFilterRender() {
const { machineNameFilterTree, machineNameOperations} = this.state;
return machineNameFilterTree && machineNameFilterTree.length !== 0 ? (
<FilterTree
filterList={machineNameFilterTree}
filterOperations={machineNameOperations}
onCheckChange={this.machineNameChange}
onExpandChange={this.machineNameExpand}
/>
) : null;
}
machineNameExpand
/**
* toggle when user expanded or collapsed machineName filter
* @param newOperetions `TreeViewOperationDescriptors` machineName operations
* *change state 'machineNameOperations'
*/
private machineNameExpand(newOperetions: TreeViewOperationDescriptors) {
this.setState({machineNameOperations: newOperetions});
}
machineNameChange
/**
* toggle when user change machineName filter
* @param newOperetions `TreeViewOperationDescriptors` machineName operations
* *change state 'machineNameOperations'
*/
private machineNameChange(newOperetions: TreeViewOperationDescriptors) {
this.setState({machineNameOperations: newOperetions}, () => {
//这里根据你自己的代码来写,就是获取到选中的machineName,然后发送请求
this.notifyFilterChange();
});
}
//下面是在我的情境中获取选中的machineName方法 ,主要是getSelectedMachineNames
private getSelectedItemsArr(operations: TreeViewOperationDescriptors, filterTree: IFilterTreeItem<string>[]) {
if (
operations.check !== undefined &&
"ids" in operations.check &&
operations.check.ids &&
operations.check.ids.length !== 0
) {
// 当length为1,说明你选中了所有项
const allSub = (operations.check.ids as string[]).filter((i) => i.length === 1);
if (allSub.length !== 0 && filterTree[0].items) {
return filterTree[0].items.map((e) => e.text);
} else {
const selectedList: string[] = [];
// geted selected item's subscript then compared with 'filterTree' to find item
operations.check.ids.forEach((e: string) => {
const selectedIndexArr = e.split("_");
// 否则,就是选中了部分项
if (selectedIndexArr.length !== 1 && filterTree[0].items) {
const sub = Number(selectedIndexArr[1]);
// 找到你选中的其中之一
const selectedItem = filterTree[0].items[sub].text;
selectedList.push(selectedItem);
}
});
return selectedList;
}
}
return [];
}
/** get user selected machineName filter item
* @param machineNameOperations `TreeViewOperationDescriptors` machine name operation
*/
private getSelectedMachineNames(machineNameOperations: TreeViewOperationDescriptors) {
const {machineNameFilterTree} = this.state;
const selectedList = this.getSelectedItemsArr(machineNameOperations, machineNameFilterTree);
return selectedList;
}
changeFormatOfMachineName(重要部分)
/** change format of machineName filter list
* @param machineNameFilterList `string[]` machineName filter list of props.filters
*/
private changeFormatOfMachineName(machineNameFilterList: string[]) {
let formatedList: IFilterTreeItem<string>[] = [];
let newMachineNames: string[] = [];
let ids: string[] = [];
//这个方法是处理旧的列表和ids, 然后得到新列表和新的ids
const {newList, newIds} = this.getNewMachineNames(machineNameFilterList);
newMachineNames = newList;
ids = newIds;
// when selected all child item , father item will be selected
if (ids.length !== 0 && newMachineNames.length !== 0 && ids.length === newMachineNames.length) {
const sub = ids.indexOf("0");
if (sub === -1) ids.push("0");
}
//这里是修改列表的数据格式
if (newList.length !== 0) {
// first level of machineName filter
formatedList = [
{
text: "MachineName",
expanded: false,
items: [],
},
];
newMachineNames.forEach((e) => {
// second level of machineName filter
(formatedList[0].items as IFilterTreeItem<string>[]).push({text: e});
});
}
const machineNameOperations = {...this.state.machineNameOperations, check: {ids, applyCheckIndeterminate: true}};
return {machineNameFilterTree: formatedList, machineNameOperations};
}
这是getNewList
/** 得到新的列表和新的ids
* @param filterList `string[]` machineName filter list of props filters
*/
private getNewMachineNames(filterList: string[]) {
let newIds: string[] = [];
let newList: string[] = filterList;
// 这里就是前面展示的获取选中项的方法,在前面可以找到
const currentSelectedItems = this.getSelectedMachineNames(this.state.machineNameOperations);
if (currentSelectedItems.length !== 0) {
/** if didn't find selected items in new list, add these items in new list */
newList = this.getNewList(newList, currentSelectedItems);
newList.sort();
// used to get new check operations
newIds = this.checkedItems(newList, currentSelectedItems);
}
return {newList, newIds};
}
/** 当你在新列表中找不到刚才选中项,那么把刚才选中项加入新列表
* @param newItemArr `T[]` new list of filter
* @param selectedItemArr `T[]` selected list of filters item
*/
private getNewList<T>(newItemArr: T[], selectedItemArr: T[]): T[] {
const newList = JSON.parse(JSON.stringify(newItemArr)) as T[];
selectedItemArr.forEach((e) => {
const item = newItemArr.filter((i) => i === e);
if (item.length === 0) {
newList.push(e);
}
});
return newList;
}
/** 修改ids
* @param newItemArr `T[]` new list of filter
* @param selectedItemArr `T[]` selected list of filters item
*/
private checkedItems<T>(newItemArr: T[], selectedItemArr: T[]): string[] {
const ids: string[] = [];
newItemArr.forEach((e, index) => {
selectedItemArr.forEach((i) => {
// if item still exist in new list, keep it as selected
if (e === i) {
ids.push(`0_${index}`);
}
});
});
return ids;
}
大部分代码在这里,阅读起来可能比较困难,建议先阅读Help-function,
具体细节请根据自己的情景加以修改
以上是我的理解,欢迎大佬指教