React Kendo Treeview scroll to item - reactjs

I am using React Kendo Treeview UI. I want to try to scroll to the item that is selected in the tree. I found many examples for Javascript and JQuery but none for React version. I couldn't solve this problem by playing around with it.
Items in the tree are of type MyViewTreeModel. I have a selectOntree method that finds a node and set the selected to true. My problem is I want to scroll to that item.
export interface MyViewTreeModel {
text: string,
expanded: boolean,
employeeId : number,
treeId: number,
items?: MyViewTreeModel [],
selected: boolean
}
....
<TreeView
data={myData}
expandIcons={true}
onExpandChange={onExpandChange}
onItemClick={OnItemClick}
aria-multiselectable={false}
aria-label={'text'}
></TreeView>
....
const selectOnTree = (employeeId: number ) => {
let treeItem = recursivelyFindEmployeeInTree(myData[0], employeeId);
treeItem.selected = true;
forceUpdate();
}
}
myData is of type MyViewTreeModel .
One solution I tried : I added ref?: any to my model and tried treeItem.ref.current.focus(); in selectOnTree function, but ref was undefined.
Another solution I tried is adding this property to TreeView:
ref={component => treeViewRef.current = component}
Then tried this just to select the first 'li' tag in the TreeView:
if(!_.isNil(treeViewRef.current) ){
let domElement = ReactDOM.findDOMNode(treeViewRef.current);
let treeItemDom = domElement.firstChild.firstChild;
(treeItemDom as HTMLElement).focus();
}
This didn't work, it doesn't put the focus at that point.
I am thinking maybe I should define a custom itemRender that has a ref that I can find the offsetTop of it, but then there are more than one item, how can I create a different ref for each one? Or maybe a custom ItemRender that renders an input (with css I can make it look like a span) and then set autofocus to true if selected is true. Not sure if autofocus true make it scroll to that item.

This is the solution I could find to make it work:
Adding a reference to TreeView
let treeViewRef = useRef(null);
In return statement:
<TreeView
data={myData}
expandIcons={true}
onExpandChange={onExpandChange}
onItemClick={OnItemClick}
aria-multiselectable={false}
aria-label={'text'}
ref={component => treeViewRef.current = component}></TreeView>
2.Defined this function to scroll to a specific treeItem:
'k-in' is the className for each span that represent each item in the Kendo Treeview UI component.
const scrollToTreeViewItem = (treeItem: MyViewTreeModel ) => {
if(!_.isNil(treeViewRef.current)){
let domElement = ReactDOM.findDOMNode(treeViewRef.current);
let treeItemDoms = (domElement as Element).querySelectorAll('.k-in');
let domArray = [];
treeItemDoms.forEach((node) => {
domArray.push(node as HTMLElement);
});
let targetedDomElement = domArray.find((item) => {
return item.innerText === treeItem.text;
});
targetedDomElement.scrollIntoView();
}
}

Related

React state value is not retained when value is set using useState within useEffect with dep []

I am using Fluent UI Details List and trying to make row editable on icon click.
My code is as below. On first run, it shows grid with empty data. Then it goes to useEffect (I tried both useEffect and useLayoutEffect, same behaviour) and data is fetched and stored in state. It also fires the render and the grid shows all the rows per the data. All good so far.
When row > cell 1 is double-clicked on the grid, I turned the row to editable mode. That is also working.
For each editable column, I have a different onChange event attached. So, when any input text box/dropdown cell value changes, it fires the respective onChange event callback function.
Within this cell change callback event, it gets item id and final changed value as input aurguments. And using them, data array will be updated and stored in state. This is my expectation.
But when I read the current data array from state within the callback function, it is empty.
So, basically, problem is that state value stored from useEffect is not retained.
There is no other code line where data array state is updated. So, no chance that the data array is reset by code.
If anyone has any idea or faced, solved such issue, let me know. Thanks in advance.
Adding few things which I tried,
I tried using the class component and it worked. only difference is that instead of useEffect, I used componentDidMount and instead of useState, I used this.setState. Not sure what is other difference within class and function component?
The same code works in class component but not in function component.
I tried using the same function component and instead of async call within useEffect, I made direct sync fetch call before render and loaded data in state as initial value. Then, it worked.
So, it fails only when the data is fetched async mode within useEffect and set to state from there.
My problem is resolved after converting to class component.
but want to understand what is the issue within my function component code.
/** function component */
const [dataItems, setDataItems] = useState<IScriptStep[]>([]);
const [groups, setGroups] = useState<IGroup[]>([]);
/** Component Did Mount */
useLayoutEffect(() => {
props.telemetry.log(`useEffect - []`, LogLevel.Debug);
(async () => {
let scriptSteps = await props.dataHelper.retrieveScriptSteps();
setDataItems(scriptSteps);
let groups = getGroups(scriptSteps);
setGroups(groups);
props.telemetry.log(`Data updated in state`, LogLevel.Debug);
})();
}, []);
/** Render */
return (
<div className="SubgridMain">
{props.telemetry.log(`render`, LogLevel.Debug)}
<div className="List">
<DetailsList
componentRef={_root}
items={dataItems}
groups={groups}
columns={columns}
ariaLabelForSelectAllCheckbox="Toggle selection for all items"
ariaLabelForSelectionColumn="Toggle selection"
checkButtonAriaLabel="select row"
checkButtonGroupAriaLabel="select section"
onRenderDetailsHeader={_onRenderDetailsHeader}
onRenderRow={_onRenderRow}
groupProps={{ showEmptyGroups: true }}
onRenderItemColumn={_onRenderItemColumn}
onItemInvoked={_onItemInvoked}
compact={false}
/>
</div>
</div>
);
/** on change cell value callback function */
const _onChangeCellName = (entityId : string, fieldName:string, finalValue:string) => {
let currentItems = dataItems;
// create new data array
let toUpdateState: boolean = false;
let newItems = currentItems.map((item) => {
if (item.key === entityId) {
if (item.name !== finalValue) {
toUpdateState = true;
item.name = finalValue ?? null;
}
}
return item;
});
if (toUpdateState) setDataItems(newItems);
};
/** columns configuration is set as below */
let columns : IColumn[] = [
{
key: 'name',
name: 'Name',
fieldName: 'name',
minWidth: 300,
isResizable: true,
onRender: this._onRenderCellName,
},
..
..
]
/** Render Name cell */
private _onRenderCellName(item?: IScriptStep, index?: number, column?: IColumn) {
if (item) {
let stepName = item?.name ?? '';
if (item.isEditable) {
let propsNameTextEditor: ITextEditorWrapperProps = {
entityId: item.key,
fieldName: 'name',
initialText: stepName,
multiline: true,
required: true,
setFinalValue: this._onChangeCellName,
};
return <TextEditorWrapper {...propsNameTextEditor} />;
} else {
return (
<div className="ReadOnly">
<p>{stepName}</p>
</div>
);
}
} else return <></>;
};

ExtJs 6 or 7 focus treelist item into view

I have this treelist with a toolbar that I use to create new elements (it's a document list) and I want to focus the newly created item. I can select it but I don't seem to find a way of focusing it:
const selected = documents.treeStore.getAt(documents.treeStore.findExact(
"documentId", current.id
));
if (selected) {
tree.cmp.setSelection(selected);
const node = tree.cmp.getSelection();// maybe redundant
// how do I focus this node into view?
}
If by focus you mean that the element should appear in the visibility range, you can use this method
ensureVisible
const selected = documents.treeStore.getAt(documents.treeStore.findExact(
"documentId", current.id
));
if (selected) {
var path = [];
do {
path.push(selected.getId());
}while (selected = selected.getRefOwner());
tree.cmp.expandPath('/' + path.reverse().join('/'), {focus:true});
}

How to check dynamically rendered checkboxes

I'm rendering some checkboxes dynamically, but currently I'm only able to check the first box, and all other boxes operate the first one. How do I get the boxes to work independently of each other?
This is typescript in React. I've tried changing the interface I'm referencing in the function, thinking I was referencing the wrong thing, but none of those worked.
This is the function:
handleCheckboxClick = (entitlement: IApiEntitlements, checked: boolean): void => {
if (checked === true) {
this.selectedEntitlementIDs.push(entitlement.id);
} else {
const index: number = this.selectedEntitlementIDs.indexOf(entitlement.id);
this.selectedEntitlementIDs.splice(index, 1);
}
//tslint:disable-next-line:prefer-const
let entitlementChecked: IEntitlementChecked = this.state.entitlementChecked;
entitlementChecked[entitlement.id] = checked;
let selectAll: boolean = false;
if (this.selectedEntitlementIDs.length === this.state.responses.apiResponses.apiClients.length) {
selectAll = true;
}
this.setState({
entitlementChecked: entitlementChecked,
selectAll: selectAll
});
console.log(this.selectedEntitlementIDs, 'hi');
console.log(entitlementChecked, 'hello');
}
And this is where it's being called:
return (
<Checkbox
checked={this.state.entitlementChecked[entitlement.id]}
data-ci-key={entitlement.id}
id='api-checkbox'
key={entitlement.id}
labelText={entitlement.label}
onChange={this.handleCheckboxClick}>
</Checkbox>
);
I expect each checkbox to be able to be checked, but currently on the first one works, and all others check or uncheck that first one.
You shouldn't keep an array as a property on the class that keeps track of selected items, this isn't tied to the React lifecycle and could potentially not update the view when you want to. Instead you should just use your map (entitlementChecked) you already have to determine if something is checked or not.
handleCheckboxClick(id) {
this.setState(prevState => ({
entitlementChecked: {
...prevState.entitlementChecked,
[id]: !prevState.entitlementChecked[id]
}
}));
}
When calling the handler method, you can just pass the id through that you need specifically.
onChange={this.handleCheckboxClick.bind(null, item.id)}
Here's a rudimentary example for more detail :)

react-native-multiple-select storing items selected on submit

I am using react-native-multiple-select and trying to create a dropdown menu that allows users to select multiple options and then logs the options they select into an array.
At the moment, my code is:
onSelectedItemsChange = selectedItems => {
this.setState({ selectedItems });
console.log('submit button was pressed')
};
render() {
const { selectedItems } = this.state;
return (
<View style={{ flex: 1 }}>
<MultiSelect
hideTags
items={items}
uniqueKey="id"
ref={(component) => { this.multiSelect = component }}
onSelectedItemsChange={this.onSelectedItemsChange}
selectedItems={selectedItems}
selectText="Pick Items"
searchInputPlaceholderText="Search Items..."
onChangeInput={ (text)=> console.log(text)}
altFontFamily="ProximaNova-Light"
tagRemoveIconColor="#CCC"
tagBorderColor="#CCC"
tagTextColor="#CCC"
selectedItemTextColor="#CCC"
selectedItemIconColor="#CCC"
itemTextColor="#000"
displayKey="name"
searchInputStyle={{ color: '#CCC' }}
submitButtonColor="#CCC"
submitButtonText="Submit"
/>
<View>
The problem is with the submit button. I only want to record the items selected once the user has pressed submit.
At the moment it logs that the button was pressed every time a new item is selected and that does not help with storing the items selected into another array.
Any help would be great.
You can do this to get an array with the item objects that are selected:
for(var i = 0; i < selectedItems.length; i++){
this.state.selectedItemsArray.push(this.state.gasOptions[selectedItems[i]])
}
console.log(selectedItems);
This should output the array of items that are selected with each item containing the unique key and the display name.
this.state.selectedItemsArray.push(listOfObject[0].id);
I noticed that the selectedItemsArray stores only the key so its an array of keys and not list of objects. Thus, if your key is id you want to push that to the array and not all the object.
I faced the same issue before. Now I fixed it.
Follow below steps:
Go to node_modules/react-native-multi-select/index.d.ts add the code
onSubmitclick: ((items: any[]) => void), inside export interface MultiSelectProps {}
Go to lib/react-native-multi-select.js add the code
onSubmitclick: PropTypes.func, inside the static propTypes ={}
Go to the function _submitSelection() and add the code inside it
const {selectedItems, onSubmitclick } = this.props; onSubmitclick(selectedItems);
Now you return your Multiselect tag add
onSubmitclick={(value1) => getSubmit(value1)}
capture your selected value with this function
const getSubmit = (value1) => { console.log('new submit value***', value1) }
I hope, It will helpful for someone.

Customer ListItem of Selectable List doesn't work in material-ui

I used the Selectable List, but if i wrote a custome listitem, the List isn't selectable. If I used listitem directly, the list is selectable.
var DataCenterRow = React.createClass({
render: function () {
return (
< ListItem primaryText = {this.props.datacenter.name}
rightIconButton= {rightIconMenu}
value={this.props.index} onTouchTap= {this.selectItem}/>
);
}
});
module.exports = DataCenterRow
If you look at the source code of makeSelectable, there is a check for muiName === 'ListItem', so make sure that your customized ListItem have type equals 'ListItem'.
ES6:
static muiName = 'ListItem';
ES5:
DataCenterRow.muiName = 'ListItem';
Don't forget to render DataCenterRow with the style got from outside (because makeSelectable will pass through selectedItemStyle to selected item)

Resources