jQuery UI Widgets Forums Grid initrowdetails does not work first time with React typescript JQXGrid

This topic contains 6 replies, has 2 voices, and was last updated by  LaxGulawani 5 years, 9 months ago.

Viewing 7 posts - 1 through 7 (of 7 total)
  • Author

  • LaxGulawani
    Participant

    we are working to implement nested grid which loads data in initrowdetails function and then we try to render jqxgrid using ReactDOM.render, like below

    ReactDOM.render(<JqxGrid source={subGridAdapter} columns={subGridColumns} />, document.getElementById(gridId));

    On the first time get an error as ‘Target container is not a DOM element’, however on the second click, it shows nested table correctly.

    I suspect it is because the div in which grid is going to be rendered is not present on DOM, so what could be an alternate solution for this?

    Thank you


    Martin
    Participant

    Hello LaxGulawani,

    The following example for nested grids of jqxGrid with React with typescript is working fine.
    It is using exactly ReactDOM.render:

    import * as React from 'react';
    import * as ReactDOM from 'react-dom';
    
    import JqxGrid, { IGridProps, jqx } from 'jqwidgets-scripts/jqwidgets-react-tsx/jqxgrid';
    
    class App extends React.PureComponent<{}, IGridProps> {
    
        private myGrid = React.createRef<JqxGrid>();
    
        constructor(props: {}) {
            super(props);
    
            const source: any =
            {
                datafields: [
                    { name: 'FirstName' },
                    { name: 'LastName' },
                    { name: 'Title' },
                    { name: 'Address' },
                    { name: 'City' }
                ],
                datatype: 'xml',
                id: 'EmployeeID',
                record: 'Employee',
                root: 'Employees',
                url: './assets/sampledata/employees.xml'
            };
    
            const ordersDetailsSource: any = {
                datafields: [
                    { name: 'EmployeeID', type: 'string' },
                    { name: 'ShipName', type: 'string' },
                    { name: 'ShipAddress', type: 'string' },
                    { name: 'ShipCity', type: 'string' },
                    { name: 'ShipCountry', type: 'string' },
                    { name: 'ShippedDate', type: 'date' }
                ],
                datatype: 'xml',
                record: 'Order',
                root: 'Orders',
                url: './assets/sampledata/orderdetails.xml'
            };
    
            const ordersDataAdapter = new jqx.dataAdapter(ordersDetailsSource, { autoBind: true });
    
            const photoRenderer = (row: number, column: any, value: string): string => {
                const name = this.myGrid.current!.getrowdata(row).FirstName;
                const imgurl = './assets/images/' + name.toLowerCase() + '.png';
                const img = '<div style="background: white;"><img style="margin: 2px; margin-left: 10px;" width="32" height="32" src="' + imgurl + '"></div>';
                return img;
            }
    
            const renderer = (row: number, column: any, value: string): string => {
                return '<span style="margin-left: 4px; margin-top: 9px; float: left;">' + value + '</span>';
            }
    
            const nestedGrids: any[] = [];
    
            const initrowdetails = (index: number, parentElement: any, gridElement: any, record: any): void => {
                const id = record.uid.toString();
                const nestedGridContainer = parentElement.children[0];
                nestedGrids[index] = nestedGridContainer;
                const filtergroup = new jqx.filter();
                const filtervalue = id;
                const filtercondition = 'equal';
                const filter = filtergroup.createfilter('stringfilter', filtervalue, filtercondition);
                // fill the orders depending on the id.
                const orders = ordersDataAdapter.records;
                const ordersbyid = [];
                for (const order of orders) {
                    const result = filter.evaluate(order.EmployeeID);
                    if (result) {
                        ordersbyid.push(order);
                    }
                }
                const ordersSource = {
                    datafields: [
                        { name: 'EmployeeID', type: 'string' },
                        { name: 'ShipName', type: 'string' },
                        { name: 'ShipAddress', type: 'string' },
                        { name: 'ShipCity', type: 'string' },
                        { name: 'ShipCountry', type: 'string' },
                        { name: 'ShippedDate', type: 'date' }
                    ],
                    id: 'OrderID',
                    localdata: ordersbyid
                }
                const nestedGridAdapter = new jqx.dataAdapter(ordersSource);
    
                if (nestedGridContainer != null) {
                    const columns: IGridProps['columns'] = [
                        { text: 'Ship Name', datafield: 'ShipName', width: 200 },
                        { text: 'Ship Address', datafield: 'ShipAddress', width: 200 },
                        { text: 'Ship City', datafield: 'ShipCity', width: 150 },
                        { text: 'Ship Country', datafield: 'ShipCountry', width: 150 },
                        { text: 'Shipped Date', datafield: 'ShippedDate', width: 200 }
                    ];
    
                    ReactDOM.render(
                        <JqxGrid width={780} height={200} source={nestedGridAdapter} columns={columns} />,
                        document.getElementById(nestedGridContainer.id)
                    );
                }
            };
    
            this.state = {
                columns: [
                    { text: 'Photo', width: 50, cellsrenderer: photoRenderer },
                    { text: 'First Name', datafield: 'FirstName', width: 100, cellsrenderer: renderer },
                    { text: 'Last Name', datafield: 'LastName', width: 100, cellsrenderer: renderer },
                    { text: 'Title', datafield: 'Title', width: 180, cellsrenderer: renderer },
                    { text: 'Address', datafield: 'Address', width: 300, cellsrenderer: renderer },
                    { text: 'City', datafield: 'City', width: 170, cellsrenderer: renderer }
                ],
                initrowdetails,
                ready: (): void => {
                   this.myGrid.current!.showrowdetails(1);
                },
                rowdetailstemplate: {
                    rowdetails: '<div id="nestedGrid" style="margin: 10px;"></div>',
                    rowdetailsheight: 220,
                    rowdetailshidden: true
                },
                source: new jqx.dataAdapter(source)
            }
        }
    
        public render() {
            return (
                <JqxGrid ref={this.myGrid}
                    // @ts-ignore
                    width={getWidth('grid')} height={365} source={this.state.source} columns={this.state.columns}
                    rowdetails={true} rowsheight={35} initrowdetails={this.state.initrowdetails}
                    ready={this.state.ready} rowdetailstemplate={this.state.rowdetailstemplate} />
            );
        }
    }
    
    export default App;

    I hope this would help you!

    Best Regards,
    Martin

    jQWidgets Team
    http://www.jqwidgets.com/


    LaxGulawani
    Participant

    Hi Martin,

    Thanks for your inputs, I guess the issue is related to fetching data dynamically for the nested grid.

    We just have minor modification in example you have provided as below:

    const initrowdetails = (index: number, parentElement: any, gridElement: any, record: any): void => {
                const id = record.uid.toString();
                const nestedGridContainer = parentElement.children[0];
                nestedGrids[index] = nestedGridContainer;
                const orderId = record.id;
                //till below call we can see nestedGridContainer is present on actual DOM
                this.props.requestDetailsByOrderId(orderId);
                // after above line execution nestedGridContainer is removed from actual DOM, which causes exeception during ReactDOM.render at the end of function.
    
                const ordersbyid = this.props.orders
    
                const ordersSource = {
                    datafields: [
                        { name: 'EmployeeID', type: 'string' },
                        { name: 'ShipName', type: 'string' },
                        { name: 'ShipAddress', type: 'string' },
                        { name: 'ShipCity', type: 'string' },
                        { name: 'ShipCountry', type: 'string' },
                        { name: 'ShippedDate', type: 'date' }
                    ],
                    id: 'OrderID',
                    localdata: ordersbyid
                }
                const nestedGridAdapter = new jqx.dataAdapter(ordersSource);
    
                if (nestedGridContainer != null) {
                    const columns: IGridProps['columns'] = [
                        { text: 'Ship Name', datafield: 'ShipName', width: 200 },
                        { text: 'Ship Address', datafield: 'ShipAddress', width: 200 },
                        { text: 'Ship City', datafield: 'ShipCity', width: 150 },
                        { text: 'Ship Country', datafield: 'ShipCountry', width: 150 },
                        { text: 'Shipped Date', datafield: 'ShippedDate', width: 200 }
                    ];
    
                    ReactDOM.render(
                        <JqxGrid width={780} height={200} source={nestedGridAdapter} columns={columns} />,
                        document.getElementById(nestedGridContainer.id)
                    );
                }
            };

    Please can you suggest why this happens or any alternate way to ensure element is not removed from actual DOM.

    Thanks again for your support.


    Martin
    Participant

    Hello LaxGulawani,

    What is happening inside the requestDetailsByOrderId method? Fetching the data should not affect the DOM at all.
    Also, there is a check: if (nestedGridContainer != null) before calling ReactDOM.render.
    Below is a sample of loading the subdata dynamically, similarly to your example.

    Please, note that in this case there is async: false set to the ordersDataAdapter, so the execution of the code does not continue
    before the data is loaded.

    import * as React from 'react';
    import * as ReactDOM from 'react-dom';
    
    import JqxGrid, { IGridProps, jqx } from 'jqwidgets-scripts/jqwidgets-react-tsx/jqxgrid';
    
    class App extends React.PureComponent<{}, IGridProps> {
        private myGrid = React.createRef<JqxGrid>();
        private subGridData: any[] = [];
    
        constructor(props: {}) {
            super(props);
            this.loadSubDataById = this.loadSubDataById.bind(this);
    
            const source: any =
            {
                datafields: [
                    { name: 'FirstName' },
                    { name: 'LastName' },
                    { name: 'Title' },
                    { name: 'Address' },
                    { name: 'City' }
                ],
                datatype: 'xml',
                id: 'EmployeeID',
                record: 'Employee',
                root: 'Employees',
                url: './assets/sampledata/employees.xml'
            };
        
    
            const photoRenderer = (row: number, column: any, value: string): string => {
                const name = this.myGrid.current!.getrowdata(row).FirstName;
                const imgurl = './assets/images/' + name.toLowerCase() + '.png';
                const img = '<div style="background: white;"><img style="margin: 2px; margin-left: 10px;" width="32" height="32" src="' + imgurl + '"></div>';
                return img;
            }
    
            const renderer = (row: number, column: any, value: string): string => {
                return '<span style="margin-left: 4px; margin-top: 9px; float: left;">' + value + '</span>';
            }
    
            const nestedGrids: any[] = [];
    
            const initrowdetails = (index: number, parentElement: any, gridElement: any, record: any): void => {
                const id = record.uid.toString();
                const nestedGridContainer = parentElement.children[0];
                nestedGrids[index] = nestedGridContainer;
    
                this.loadSubDataById(id);
    
                const ordersbyid = this.subGridData;
                
                const ordersSource = {
                    datafields: [
                        { name: 'EmployeeID', type: 'string' },
                        { name: 'ShipName', type: 'string' },
                        { name: 'ShipAddress', type: 'string' },
                        { name: 'ShipCity', type: 'string' },
                        { name: 'ShipCountry', type: 'string' },
                        { name: 'ShippedDate', type: 'date' }
                    ],
                    id: 'OrderID',
                    localdata: ordersbyid
                }
                const nestedGridAdapter = new jqx.dataAdapter(ordersSource);
    
                if (nestedGridContainer != null) {
                    const columns: IGridProps['columns'] = [
                        { text: 'Ship Name', datafield: 'ShipName', width: 200 },
                        { text: 'Ship Address', datafield: 'ShipAddress', width: 200 },
                        { text: 'Ship City', datafield: 'ShipCity', width: 150 },
                        { text: 'Ship Country', datafield: 'ShipCountry', width: 150 },
                        { text: 'Shipped Date', datafield: 'ShippedDate', width: 200 }
                    ];
    
                    ReactDOM.render(
                        <JqxGrid width={780} height={200} source={nestedGridAdapter} columns={columns} />,
                        document.getElementById(nestedGridContainer.id)
                    );
                }
            };
    
            this.state = {
                columns: [
                    { text: 'Photo', width: 50, cellsrenderer: photoRenderer },
                    { text: 'First Name', datafield: 'FirstName', width: 100, cellsrenderer: renderer },
                    { text: 'Last Name', datafield: 'LastName', width: 100, cellsrenderer: renderer },
                    { text: 'Title', datafield: 'Title', width: 180, cellsrenderer: renderer },
                    { text: 'Address', datafield: 'Address', width: 300, cellsrenderer: renderer },
                    { text: 'City', datafield: 'City', width: 170, cellsrenderer: renderer }
                ],
                initrowdetails,
                ready: (): void => {
                    this.myGrid.current!.showrowdetails(1);
                },
                rowdetailstemplate: {
                    rowdetails: '<div id="nestedGrid" style="margin: 10px;"></div>',
                    rowdetailsheight: 220,
                    rowdetailshidden: true
                },
                source: new jqx.dataAdapter(source)
            }
        }
    
        public render() {
            return (
                <JqxGrid ref={this.myGrid}
                    // @ts-ignore
                    width={getWidth('grid')} height={365} source={this.state.source} columns={this.state.columns}
                    rowdetails={true} rowsheight={35} initrowdetails={this.state.initrowdetails}
                    ready={this.state.ready} rowdetailstemplate={this.state.rowdetailstemplate} />
            );
        }
    
        private loadSubDataById(id: any) {      
            const ordersDetailsSource: any = {
                datafields: [
                    { name: 'EmployeeID', type: 'string' },
                    { name: 'ShipName', type: 'string' },
                    { name: 'ShipAddress', type: 'string' },
                    { name: 'ShipCity', type: 'string' },
                    { name: 'ShipCountry', type: 'string' },
                    { name: 'ShippedDate', type: 'date' }
                ],
                datatype: 'xml',
                record: 'Order',
                root: 'Orders',
                url: './assets/sampledata/orderdetails.xml'
            };
    
            const ordersDataAdapter = new jqx.dataAdapter(ordersDetailsSource, { autoBind: true, async: false });
    
            const filtergroup = new jqx.filter();
            const filtervalue = id;
            const filtercondition = 'equal';
            const filter = filtergroup.createfilter('stringfilter', filtervalue, filtercondition);
    
            const orders = ordersDataAdapter.records;
            for (const order of orders) {
                const result = filter.evaluate(order.EmployeeID);
                if (result) {
                    this.subGridData.push(order);
                }
            }
        }
    }
    
    export default App;

    Best Regards,
    Martin

    jQWidgets Team
    http://www.jqwidgets.com/


    LaxGulawani
    Participant

    Hi Martin,

    Below is the method for actionCreator

    requestDetailsByOrderId: (id: number):
            AppThunkAction<KnownAction> => (dispatch, getState) => {
                if (orderNumber !== getState().orders.orderNumber) {
                    const fetchTask = fetch(<code>api/orders/GetOrderDetailsForOrderId/${id}</code>)
                    .then((response) => response.json() as Promise<Order[]>)                
                    .then((data) => {
                        dispatch({ type: 'RECEIVE_ORDERBYORDERID',
                                   id,                               
                                   orders: data },
                                );
                    });
                    addTask(fetchTask);
                    dispatch({ type: 'REQUEST_ORDERBYORDERID', id});
                }
        }

    As you have mentioned calling this method should not impact DOM in any way. however, currently it is causing a problem.

    Thanks for your prompt response.


    Martin
    Participant

    Hello LaxGulawani,

    What I suppose that may be the reason is that when you are fetching the data, the state gets updated,
    so the component is re-rendered which will result in the rowdetails not being open.
    And as you mentioned you are getting an error on the first click only, because after that you have the data already.

    Best Regards,
    Martin

    jQWidgets Team
    http://www.jqwidgets.com/


    LaxGulawani
    Participant

    Hi Martin,

    We have relooked at the problem and decided not to update state while displaying nested grid. That solved problem.

    Now we are binding ajax result directly to nested grid and it works well.

    Thanks for your help.

Viewing 7 posts - 1 through 7 (of 7 total)

You must be logged in to reply to this topic.