KoolReport's Forum

Official Support Area, Q&As, Discussions, Suggestions and Bug reports.
Forum's Guidelines

Search is breaking Datagrid table when ClientRowGroup is used #2726

Open George opened this topic on on Jun 14, 2022 - 9 comments

George commented on Jun 14, 2022

I have a search input and function thats running every keyup.

                <input id="input_search" placeholder="Search" onkeyup="onGroupTableSearchChanged(this)">
function onGroupTableSearchChanged(el) {
    const text = $(el).val();
    reportTable.search(text).draw();
}

This works fine on normal tables without clientrowgroup. But with it it seems to only grab to level-0 row

For example, if i search 'Geralt', it will grab the first row dtrg-start, but NOT the 4 corresponding rows that are technically a part of the rowGroup. Is there a way I can draw the entire group in the provided function?

Thank you

Sebastian Morales commented on Jun 16, 2022

Pls try to add a hidden ("visible" => false) but searchable ("searchable" => true) column that contains the value "Geralt" to your DataTables' "columns". If it doesn't work let us know your DataTables' code and a not-working example. Rgds,

George commented on Jun 16, 2022

Unfortunately that did not work.

Here is the Datatable Code:

DataTables::create(
                "name"       => "reportTable",
                "dataStore"  => $this->dataStore("result"),
                "cssClass"   => [
                    "table" => "display table table-bordered table-hover",
                    "th"    => function ($colName) {
                        $className = str_replace(' ', '-', strtolower($colName));
                        $hide_header = $colName == "Question" || $colName == "Answer" ? "hide_cell" : "";
                        return "reportTableHeader {$className} {$hide_header}";
                    },
                    "tr"    => 'reportTableRow',
                    'td'    => function ($row, $colName) {
                        $hide_cell = $colName == "Question" || $colName == "Answer" ? "" : "hide_cell";
                        $className = str_replace(' ', '-', strtolower($colName));
                        return "reportTableCell {$className} {$hide_cell}";
                    },
                    'tf'    => "reportTableFooter",
                ],
                "attributes" => [
                    "class" => "form-control",
                    "td" => function($row, $colKey, $colMeta) {
                        if($colKey == 'Question'){
                            return [
                                "colspan" => 6
                            ];
                        }
                    },
                ],
                "plugins"    => ["Buttons"],

                "clientRowGroup" => [
                    "date_cid" => [
                        'calculate' => [
                            'first_name' => [
                                "aggregate" => "function(rows, group, aggFieldIndex) {
                                    return rows.data().pluck(aggFieldIndex)[0]
                                }
                                ",
                                 'field' => 'child_first_name',
                            ],
                            'last_name' => [
                                "aggregate" => "function(rows, group, aggFieldIndex) {
                                    return rows.data().pluck(aggFieldIndex)[0]
                                }
                                ",
                                 'field' => 'child_last_name',
                            ],
                            'date' => [
                                "aggregate" => "function(rows, group, aggFieldIndex) {
                                    return rows.data().pluck(aggFieldIndex)[0]
                                }
                                ",
                                 'field' => 'Date',
                            ],
                            'location' => [
                                "aggregate" => "function(rows, group, aggFieldIndex) {
                                    return rows.data().pluck(aggFieldIndex)[0]
                                }
                                ",
                                 'field' => 'Location',
                            ],
                            'room' => [
                                "aggregate" => "function(rows, group, aggFieldIndex) {
                                    return rows.data().pluck(aggFieldIndex)[0]
                                }
                                ",
                                 'field' => 'Room',
                            ],
                            'prescreen_status' => [
                                "aggregate" => "function(rows, group, aggFieldIndex) {
                                    var answer_arr = rows.data().pluck(9).toArray().map( x =>  x.replace(/<\/?[^>]+(>|$)/g, '').replace(/[\\r\\n]/gm, '').replaceAll(' ',''))
                                    if(answer_arr.indexOf('Yes') > -1){
                                        return 'Failed'
                                    }
                                    return 'Pass'
                                }
                                ",
                                'field' => 'Answer',
                            ],
                            'prescreen_status_color' => [
                                "aggregate" => "function(rows, group, aggFieldIndex) {
                                    var answer_arr = rows.data().pluck(9).toArray().map( x =>  x.replace(/<\/?[^>]+(>|$)/g, '').replace(/[\\r\\n]/gm, '').replaceAll(' ',''))
                                    if(answer_arr.indexOf('Yes') > -1){
                                        return '#CA1E2F'
                                    }
                                    return '#88B73E'
                                }
                                ",
                                'field' => 'Answer',
                            ],
                            'dtgroups_level1_count' => [
                                'field' => 'Row',
                                "aggregate" => "function(rows, group, aggFieldIndex) {
                                    // there's one hidden 'level' variable indicating which level variable 'group' is
                                    if (level != 0) return; // don't count variable 'group' if it's not level 2 (index 1)
                                    if (!window.dtGroups_level1) window.dtGroups_level1 = [];
                                    if (window.dtGroups_level1.indexOf(group) === -1) window.dtGroups_level1.push(group);
                                    return window.dtGroups_level1.length;
                                }",
                            ], 
                        
                        ],
                        "direction" => "DESC",
                        "top" => "<td style='text-align:center;font-size:13px;font-weight:normal;'>{expandCollapseIcon} {dtgroups_level1_count}</td><td  style='font-size:12;'>{date}</td><td style='font-size:12;'>{first_name}</td><td style='font-size:12;'>{last_name}</td><td style='font-size:12;'>{location}</td><td style='font-size:12;'>{room}</td><td style='font-weight:bold;font-size:12;color:{prescreen_status_color}'>{prescreen_status}</td>"
                    ]
                ],

                "options"    => [
                    "language"   => [
                        "emptyTable" => $emptyTableText,
                    ],
                    "order"      => [
                        [0, "asc"]
                    ],
                    "select"     => TRUE,
                    "searching"  => TRUE,
                    "ordering"   => FALSE,
                    "dom"        => 'Blfrtip',
                    "buttons"    => [
                        "colvis",
                        [
                            "extend"        => "csvHtml5",
                            "footer"        => "true",
                            'exportOptions' => [
                                "columns" => ':visible',
                                "format"  => [
                                    "body" => "function ( data, row, column, node ) { return exportDatatableFormatBody(data, row, column, node); }"
                                ]
                            ]
                        ],
                        [
                            "extend"        => "excelHtml5",
                            "footer"        => "true",
                            'exportOptions' => [
                                "columns" => ':visible',
                                "format"  => [
                                    "body" => "function ( data, row, column, node ) { return exportDatatableFormatBody(data, row, column, node); }"
                                ]
                            ]
                        ],
                        [
                            'extend'        => 'pdfHtml5',
                            'orientation'   => 'landscape',
                            'pageSize'      => 'Legal',
                            'text'          => 'PDF',
                            'footer'        => TRUE,
                            'exportOptions' => [
                                "columns" => [':visible'],
                                "format"  => [
                                    "body" => "function ( data, row, column, node ) { return exportDatatableFormatBody(data, row, column, node); }"
                                ]
                            ],
                            "customize"     => "function (doc) { exportPdfCustomize(doc) }"
                        ],
                        [
                            "extend"        => "print",
                            'footer'        => TRUE,
                            'exportOptions' => [
                                "columns" => [':visible'],
                                "format"  => [
                                    "body" => "function ( data, row, column, node ) { return exportDatatableFormatBody(data, row, column, node); }"
                                ]
                            ],
                            "customize"     => "function (win) { printDatatableCustomize(win) }"

                        ],
                    ],
                    "scrollX"    => TRUE,
                    "fastRender" => TRUE,
                ],
                "onReady"    => "function(){

                    hideLoader();                
                    
                    reportTable.on( 'draw', function () {
                        collapseAllGroups('reportTable', 0);
                    } );
                    collapseAllGroups('reportTable', 0);


                    reportTable.on( 'click', 'tbody tr.dtrg-level-0', function () {
                        if (typeof expandCollapse !== 'undefined') return; // needs this since evoking expand/collapse icons' click can trigger this td's onclick function as well. We only want this function to run once.
                        expandCollapse = true;
                        var expandIcon = this.querySelector('span.group-expand');
                        if (expandIcon && expandIcon.style.display !== 'none') {
                            if(this.lastChild.innerHTML == 'Pass'){
                                this.style.color = 'white';
                                this.style.background = 'rgb(135, 182, 62)'
                            }else{
                                this.style = 'color: white !important; background:#c31b2a;'
                            }
                            expandIcon.click();
                        }else {
                            var collapseIcon = this.querySelector('span.group-collapse');
                            this.style.background = 'rgb(255, 255, 255)';
                            this.style.color = '#666'
                            if (collapseIcon && collapseIcon.style.display !== 'none') collapseIcon.click();
                        }
                        delete expandCollapse;
                    } );

                    initColumnsDropdown();
                }
                ",
    "columns" => [

        "indexColumn"    => ["label" => "Row", "formatValue" => function ($value, $row) {
            return "";
        }],
        "date_cid" => ["visible" => false],
        "Date",
        "child_first_name" => ["label" => "Child First Name", 'searchable' => true],
        "hidden_first_name" => ['visible' => false, 'searchable' => true],
        "child_last_name"=> ["label" => "Child Last Name"],
        "Location",
        "Room",
        "Pre-Screen Status",
        "Question",
        "Answer"
    ]

        )
    );

This is what it looks like when i first load the table

When I add a search term it will correctly draw the row, but the row expansion no longer works, when i click it nothing will happen

This is what its supposed to look like when expanded

I suspect some code in the onReady function is messing with it.

Sebastian Morales commented on Jun 17, 2022

Pls add onclick event directly in your groups' top row html instead of using event listener in onReady. Put the expand/collapse code to a function and use:

                        "top" => "<td colspan='999' onclick=\"expandCollapseGroup(this);\"><span style='display:none'>{expandCollapseIcon}</span> ...

The reason event listener doesn't work is because initially the top rows of row groups of latter pages haven't been rendered yet while onReady only runs once at first page loading.

George commented on Jun 27, 2022

Hi, I've added the onclick event to one of the td elements in the top row but the issue still persists.

                        "top" => "<td style='text-align:center;font-size:13px;font-weight:normal;' >{expandCollapseIcon} {dtgroups_level1_count}</td><td  style='font-size:12;'onclick='expandCollapseGroup(this)'>{date}</td><td style='font-size:12;'>{first_name}</td><td style='font-size:12;'>{last_name}</td><td style='font-size:12;'>{location}</td><td style='font-size:12;'>{room}</td><td style='font-weight:bold;font-size:12;color:{prescreen_status_color}'>{prescreen_status}</td>"

I've moved the expandCollapseGroup function to my script

    function expandCollapseGroup(element){
        var row = element.parentElement
        if (typeof expandCollapse !== 'undefined') return; // needs this since evoking expand/collapse icons' click can trigger this td's onclick function as well. We only want this function to run once.
        expandCollapse = true;
        var expandIcon = row.querySelector('span.group-expand');
        if (expandIcon && expandIcon.style.display !== 'none') {
            if(row.lastChild.innerHTML == 'Pass'){
                row.style.color = 'white';
                row.style.background = 'rgb(135, 182, 62)'
            }else{
                row.style = 'color: white !important; background:#c31b2a;'
            }
            expandIcon.click();
        }else {
            var collapseIcon = row.querySelector('span.group-collapse');
            row.style.background = 'rgb(255, 255, 255)';
            row.style.color = '#666'
            if (collapseIcon && collapseIcon.style.display !== 'none') collapseIcon.click();
        }
        delete expandCollapse;
    }

And expanding and collapsing rows only works before I try using the search feature. Once I start searching for a text, say 'Geralt', It will draw the main row group, but once I try clicking the row to expand, nothing will happen.

I've noticed before I use the search feature, the data-layer levels are correct where data-layer='0' as shown in the screenshot below

When I expand, i get the correct change where data-layer='1'.

However, after I've used the search feature, the data-layer goes to a negative value and never returns to a positive value, even when I remove the search text from the input bar.

When I click to expand, the data-layer goes from -6 to -5, the row group correctly changes background color due to the logic in my expandCollapseGroup function, but the row doesnt actually expand.

Sebastian Morales commented on Jun 28, 2022

This is probably a bug when searching while there's at least one collapsed row group. The group expand/collapse state is reset while its rows' state are not. Pls try this fix for the time being:

DataTables::create(array(
    "name" => "reportTable",
    ...
    "onReady" => "function() {
        reportTable.on( 'search.dt', function () {
            var rows = document.querySelectorAll('#reportTable tr[role=\"row\"]');
            rows.forEach(row => {
                delete row.dataset.layer;
                row.style.display = '';
            });
        } ).draw();
    }", 
));

Let us know if there's still any issue. Tks,

George commented on Jun 28, 2022

Hi,

the fix worked thank you.

However now when the table is first drawn, every row number is set to the total number of rows.

I should note im using the fixed order column feature for the row numbers with

//onReady
                    reportTable.on('order.dt search.dt', function () {
                        reportTable.column(0, {search: 'applied', order: 'applied'}).nodes().each(function (cell, i) {
                            cell.innerHTML = (i + 1).toString();
                        });
                    }).draw();
.....
//Columns array=
        "indexColumn"    => ["label" => "Row", "formatValue" => function ($value, $row) {
            return "";
        }],

Can this code be integrated with the fix you provided?

Sebastian Morales commented on Jun 29, 2022

You can try to combine the 2 "search.dt" event listener function into one like this:

    "onReady" => "function() {
        reportTable.on( 'search.dt', function () {
            var rows = document.querySelectorAll('#reportTable tr[role=\"row\"]');
            rows.forEach(row => {
                delete row.dataset.layer;
                row.style.display = '';
            });

                        reportTable.column(0, {search: 'applied', order: 'applied'}).nodes().each(function (cell, i) {
                            cell.innerHTML = (i + 1).toString();
                        });
        } ).draw();
    }",  

Let us know the result. Tks,

George commented on Jun 29, 2022

Sorry my mistake, the row value being shown is actually being calculated in the clientRowGroup Aggregate function

                            'dtgroups_level1_count' => [
                                'field' => 'Row',
                                "aggregate" => "function(rows, group, aggFieldIndex) {
                                    // there's one hidden 'level' variable indicating which level variable 'group' is
                                    if (level != 0) return; // don't count variable 'group' if it's not level 2 (index 1)
                                    if (!window.dtGroups_level1) window.dtGroups_level1 = [];
                                    if (window.dtGroups_level1.indexOf(group) === -1) window.dtGroups_level1.push(group);
                                    return window.dtGroups_level1.length;
                                }",
                            ], 

And the value is being displayed with the top

                        "top" => "<td style='text-align:center;font-size:13px;font-weight:normal;' >{expandCollapseIcon} {dtgroups_level1_count}</td><td  style='font-size:12;' onclick='expandCollapseGroup(this)';>{date}</td><td style='font-size:12;' onclick='expandCollapseGroup(this)';>{first_name}</td><td style='font-size:12;' onclick='expandCollapseGroup(this)';>{last_name}</td><td style='font-size:12;' onclick='expandCollapseGroup(this)';>{location}</td><td style='font-size:12;' onclick='expandCollapseGroup(this)';>{room}</td><td style='font-weight:bold;font-size:12;color:{prescreen_status_color}' onclick='expandCollapseGroup(this)';>{prescreen_status}</td>"

With the first page of the report it shows every row as the max row number 25.

When I change the page, it correct displays the row number

Also when I change the page back to Page 1, it makes every value the max row value from the previous page

Any idea why its causing this?

Sebastian Morales commented on Jun 30, 2022

It looks like we need to reset dtGroups_level1 on every search event:

                "onReady" => "function() {
                    ...
                    reportTable.on( 'search.dt', function () {
                        window.dtGroups_level1 = [];
                    } ).draw();
                }", 

Build Your Excellent Data Report

Let KoolReport help you to make great reports. It's free & open-source released under MIT license.

Download KoolReport View demo
None yet

None