I have a question about TreeList wrapper component. I need exactly this component.
https://www.telerik.com/kendo-react-ui/wrappers/treelist/editing/
But is it possible to do drag&drop there? I need to move objects only in their own array, I dont need them to move to their parent / child.
Another thing is that I want to active inline edit just by click on some cell, as you can see here: https://www.telerik.com/kendo-react-ui/components/grid/editing/editing-in-cell/.
If those two requests arent possible in this TreeList component, is it somehow possible to manage that via react grid component since it supports inline edit and drag&drop, but problem for me is, that each detail grid creates his own table which I dont need.
12 Answers, 1 is accepted
Regarding the requirements:
1) The TreeList currently only supports drag and drop between parents/child rows as the main point for the drag and drop feature is to re-arrange hierarchical elements. In the Grid, this is achieved using the Sortable widget, but unfortunately, this widget is currently not available for the React suite.
I can suggest commenting and voting for a similar feature request as this will increase its chances to be implemented sooner:
http://kendoui-feedback.telerik.com/forums/127393-kendo-ui-feedback/suggestions/9269412-make-drag-drop-behaviour-on-treelist-same-as-on-tr
2) The incell editing is planned for the next official release(expected around September).
Let me know if you need additional details on this matter.
Regards,
Stefan
Progress Telerik
Thanks for this information,
anyway I have one more question. Is it possible to send my custom DOM into column ( in this case, its named field ). Is it possible to call "test" function, which returns JSX.Element, which has to be render in specific column? I know that function is being called, but the function result never appears in column. Here is my code below
export default class TreeListContainer extends React.Component {
dataSource: kendo.data.TreeListDataSource;
columns: {
field?: string;
title?: string;
command?: Array<
any
>;
}[];
data: Array<
IDummyData
>;
constructor(props) {
super(props);
this.columns = [
{ field: "id" },
{ field: "name" },
{ field: "description" },
{ title: "isDefault" },
{ title: "Edits", command: ["edit"] }
]
this.dataSource = new kendo.data.TreeListDataSource({
change: this.onChange,
data: dataTree,
schema: {
model: {
id: "id",
expanded: true,
fields: {
id: { type: "string", editable: false, nullable: false },
name: { type: "string", editable: true, nullable: false },
description: { type: "string", editable: true, nullable: false },
isVisible: { type: "boolean", editable: true, nullable: false },
isDefault: this.test(),
},
}
}
});
}
private test = () => {
console.log("test render");
return <
div
>custom dom contains radio / checkbox</
div
>
}
private editCell = (event) => {
console.log("editcell", event);
}
private onChange = (event) => {
console.log("onChange", event)
}
private drag = (event) => {
console.log("drag", event)
}
private dragStart = (event) => {
console.log("drag", event)
}
private dragEnd = (event) => {
console.log("drag", event)
}
render() {
return (
<
div
>
<
TreeList
height={600}
edit={this.editCell}
dragstart={this.dragStart}
dragend={this.dragEnd}
drag={this.drag}
editable={true}
columns={this.columns}
dataSource={this.dataSource}
/>
</
div
>
);
}
}
{ title: "isDefault", template: "<
input
type
=
'checkbox'
onChange={this.test}
data-bind
=
'checked: checked'
/>"},
Binding to React functions will require using jQuery as well. The main reason is that the wrapper is based on jQuery and the templates are not aware of React specific syntax as onChange, className etc.
I can suggest attaching event listeners to the checkbox on the TreeList dataBound event:
https://docs.telerik.com/kendo-ui/api/javascript/ui/treelist/events/databound
I made an example demonstrating this:
https://next.plnkr.co/edit/iP78i7PL4WyPWpcR
Regards,
Stefan
Progress Telerik
I managed to do that with different way, but it has serious performance issue, in my case i bind action events like below in my code. I tested it also with what you send in your previous post, but it was identical result. I have around 200 rows in tree list, every time I click on checkbox or radio, doesnt matter, it has like 0.3s delay because of event handlers binding. I had to put TreeListDataSource into render method, because when I load data from my api, when I had it in constructor, treelist component never showed any of them, it just message me there is nothing to display.
But here is the issue, whenever my state is updated (click on checkbox etc..) it fires render method which creates new TreeListDataSource object and all bindings has to be done again to my controls and this cause serious performance issue. Is there something I can do to avoid it?
import React from
'react'
;
import ReactDOM from
'react-dom'
;
import $ from
'jquery'
;
import
'@progress/kendo-ui'
;
import { RouteComponentProps, Link, Route } from
"react-router-dom"
;
import * as Routes from
"routes"
;
import { TreeList, TreeListColumn } from
'@progress/kendo-treelist-react-wrapper'
;
import ReactDOMServer from
'react-dom/server'
;
import { Application } from
"core"
;
import { baseUrl } from
'domain-task'
;
import { Button } from
"@progress/kendo-react-buttons"
;
import { Resource } from
"resource"
;
interface IDummyData {
id: string;
name: string;
position?: number;
parentId: string;
isVisible?: boolean;
isDefault?: boolean;
collectionName: string;
}
interface IState {
data: Array<any>;
filter: Filter;
}
export
default
class TreeListContainer extends React.Component<{}, IState> {
checkBoxArray: Array<string> = [];
radioBoxArray: Array<string> = [];
inputNameArray: Array<string> = [];
dataSource =
new
kendo.data.TreeListDataSource();
columns: {
field?: string;
title?: string;
command?: Array<any>;
render?: any;
template?: any;
onClick?: any;
}[];
constructor(props) {
super
(props);
this
.state = {
data: [],
filter:
new
Filter()
}
this
.columns = [
{ field:
"name"
, template:
this
.renderInput },
{ title:
"Is visible"
, template:
this
.renderVisibilityCheckBox },
{ title:
"is default"
, template:
this
.renderIsDefaultRadioButton },
{ title:
"Details"
, template:
this
.renderDetailButton }
]
this
.dataSource =
new
kendo.data.TreeListDataSource({
change:
this
.onChange,
data:
this
.state.data,
schema: {
model: {
id:
"id"
,
expanded:
true
,
fields: {
id: { type:
"string"
, editable:
false
, nullable:
false
},
name: { type:
"string"
, editable:
true
, nullable:
false
},
isDefault: {
type:
"boolean"
, editable:
true
, nullable:
false
}
},
}
}
});
}
private convertDataFromApi = (array: Array<any>) => {
let testData: Array<IDummyData> = [];
function
recursiveDataBuilder(array: Array<any>) {
for
(
var
i = 0; i < array.length; i++) {
const dummy: IDummyData = {
id: array[i].key.id,
description:
""
,
isVisible: array[i].isVisible ? array[i].isVisible :
false
,
isDefault: array[i].isDefault ? array[i].isVisible :
false
,
name: array[i].name.translations[0].value,
parentId: array[i].parentKey ? array[i].parentKey.id :
null
,
position: 0,
collectionName: array[i].key.collectionName
}
testData.push(dummy);
if
(array[i].nodes
instanceof
Array && array[i].nodes && array[i].nodes.length > 0) {
recursiveDataBuilder(array[i].nodes)
}
}
}
recursiveDataBuilder(array);
return
testData;
}
private getData = () => {
Application.modelTreeClient.readModelTree(
this
.state.filter)
.then((data) => {
this
.setState(prevState => ({
...prevState,
data:
this
.convertDataFromApi(data)
}))
}).
catch
(() => alert(
"failed to get data"
));
}
componentWillMount() {
this
.getData();
}
componentDidMount() {
this
.initializeEventListeners();
}
// here i create binding via eventListeners
private initializeEventListeners = () => {
//for each checkbox add listener for events
this
.checkBoxArray.map((checkboxId: string) => {
const element = document.getElementById(checkboxId);
if
(element) {
element.addEventListener(
'change'
,
this
.visibilityCheckEvent);
}
});
//for each radio add listener for events
this
.radioBoxArray.map((radioId: string) => {
const element = document.getElementById(radioId);
if
(element) {
element.addEventListener(
'change'
,
this
.isDefaultCheckEvent);
}
});
//for each input add listener for events
this
.inputNameArray.map((id: string) => {
const element = document.getElementById(id);
if
(element) {
element.addEventListener(
'click'
,
this
.isNameChangeEvent);
element.addEventListener(
'blur'
,
this
.saveInputChange);
}
});
}
componentDidUpdate() {
const arrayOfDetailButtons = document.getElementsByTagName(
"button"
);
for
(
var
i = 0; i < arrayOfDetailButtons.length; i++) {
arrayOfDetailButtons[i].addEventListener(
'click'
,
this
.goToDetail);
}
this
.initializeEventListeners();
}
private renderInput = (item) => {
const input =
this
.addNameInputIdToArray(item);
const box = <input id={input}
onClick={
this
.isNameChangeEvent}
data-id={item.id}
readOnly={
true
}
contentEditable={
false
}
value={item.name}
className=
"k-treelist-input"
onBlur={
this
.saveInputChange}
/>
return
ReactDOMServer.renderToString(box);
}
private renderDetailButton = (item) => {
const button = <Button className=
"k-primary goto-Detail"
onClick={
this
.goToDetail}
data-contextid={item.id}
data-parentid={item.parentId}
data-contextname={item.collectionName}>
EDIT
</Button>
return
ReactDOMServer.renderToString(button);
}
private renderVisibilityCheckBox = (item) => {
const checkboxID =
this
.addVisibilityIdToArray(item);
const box = <label className=
"sa-radio-option-wrapper"
>
<input type=
"checkbox"
id={checkboxID}
className=
"k-checkbox"
onChange={
this
.visibilityCheckEvent}
data-id={item.id}
checked={item.isVisible} />
<label className=
"k-checkbox-label"
htmlFor={checkboxID}></label>
</label>
const dom = ReactDOMServer.renderToString(box);
return
dom;
}
private renderIsDefaultRadioButton = (item) => {
const defaultId =
this
.addDefaultIdToArray(item);
const box = <label className=
"sa-radio-option-wrapper"
>
<input type=
"radio"
id={defaultId}
name={item.collectionName + item.parentId}
onChange={
this
.isDefaultCheckEvent}
data-id={item.id}
checked={item.isDefault}
className=
"k-radio"
/>
<label className=
"k-radio-label"
htmlFor={defaultId}></label>
</label>
return
ReactDOMServer.renderToString(box)
}
// here i just hardcoded some array update to check if rest of the logic does not affect performance
private visibilityCheckEvent = (event) => {
const test = [...
this
.state.data];
test[0].isVisible =
false
;
this
.setState(prevState => ({
...prevState,
data: test
}));
console.log(
"updated"
);
}
/**
* add element id of every checkbox from treelist into array for later usage
*/
private addVisibilityIdToArray = (item: any): string => {
const checkboxId = `visibility_${item.id}`;
this
.checkBoxArray.push(checkboxId);
return
checkboxId;
}
private addNameInputIdToArray = (item: any): string => {
const checkboxId = `name_${item.id}`;
this
.inputNameArray.push(checkboxId);
return
checkboxId;
}
/**
* add element id of every radio button from treelist into array for later usage
*/
private addDefaultIdToArray = (item: any): string => {
const defaultId = `default_${item.id}`;
this
.radioBoxArray.push(defaultId);
return
defaultId;
}
render() {
const dataSource =
new
kendo.data.TreeListDataSource({
change:
this
.onChange,
data:
this
.state.data,
schema: {
model: {
id:
"id"
,
expanded:
true
,
fields: {
id: { type:
"string"
, editable:
false
, nullable:
false
},
name: { type:
"string"
, editable:
true
, nullable:
false
},
description: { type:
"string"
, editable:
true
, nullable:
false
},
isDefault: {
type:
"boolean"
, editable:
true
, nullable:
false
}
},
}
}
});
return
(
<div>
<h2>{Resource.TitleModelTree}</h2>
<TreeList height={600}
edit={
this
.editCell}
dragstart={
this
.dragStart}
dragend={
this
.dragEnd}
drag={
this
.drag}
columns={
this
.columns}
editable={
true
}
dataSource={dataSource}
/>
</div>
);
}
}
Thank you for the provided details and code.
I modified the previous example and added 1000 rows, but the clicking of the checkbox is instant as expected:
https://next.plnkr.co/edit/iP78i7PL4WyPWpcR
If possible, please provide us an example reproducing the issue and we will be happy investigate it locally the make suggestion on how it can be optimized.
Regards,
Stefan
Progress Telerik
Hi,
I checked your sample, there is one big difference, in your sample, checkbox does not affect state of component, but in my sample, it affects, so whenever I click on checkbox, my state is updated and it force component to re-render and this is why it slows so much, so re-render is big issue and cost a lot of time.
Best regards.
Martin.
Thank you for the advice.
I added the logic to change the state on the change events as well, and the performance was not changed noticeably:
https://next.plnkr.co/edit/XDSxMmp6q1amEmyD
As the issue could be caused by an unnoticed part of the code, if an example is provided we will be able to test different parts of the code and locate it.
Regards,
Stefan
Progress Telerik
I found an issue in my case, there was a problem that in my render function of class I all the time initialized dataSource object which is required for treelist component, because when I initialized it in constructor, it never had data, which come from api as asynchronous request. I solved it with initializing dataSource object in class constructor and passing data to it via data() function in render method.
So something like this
this.state.dataSource.data(this.state.data);
After I solved this, I found one thing, which currently is problem for us. Whenever I click on some checkbox etc, it changes state and it force component to re-render and there is problem with expanded option property, lets say, I go deeper in tree, then I click on some checkbox and it force render function and whole tree is collapsed again and vice versa. Can I somehow specify, what should collapse and what not?
Best regards.
Martin
I'm happy to hear that the initial issue when updating is resolved.
As for the collapsed state. In general, setting the state should not change the collapsed and expanded state. I made a video demonstrating this:
https://www.screencast.com/t/u90SMoQC60g6
Still, if due to another action in the application the expanded collapsed state is lost a custom logic can be used to store the state in a variable or local storage.
More details can be found in the following forum. The post is for the jQuery widgets, but the wrapper is based on the jQuery widget and the implementation is the same for the React wrapper:
https://www.telerik.com/forums/restoring-treelist-expanded-state-after-refresh
Regards,
Stefan
Progress Telerik
Hi,
sorry for late respond, I havent been here for a while. We have implemented our custom logic for it, since our component has complex logic
It is good to hear that the custom implementation is successfully able to achieve the desired result.
If you have any further questions regarding the TreeView or any other of the React components we are here to assist.
Regards,
Stefan
Progress Telerik