Freezing Columns in Report

I want to freeze the first two columns of a report when scrolling horizontally and vertically.

I implemented this. But it is getting possible only for those no of rows which are visible initially for the first time (without scrolling).
But when we scroll down columns are not visible.

5bf440d00d95d840277763

I want the first two columns of a report freeze when scrolling horizontally.
And also when scrolling vertically, one should get respective data of particular column.

Thank You.

2 Likes

What’s ur erpnext version ?

Version 10.1.20

I have made it worked.
Here’s my approach.

first backup slickgrid:
frappe-bench/apps/frappe/frappe/public/js/lib/slickgrid

then use my slickgrid repos instead:
GitHub - magic-overflow/slickgrid

I made changes on frappe-bench/apps/frappe/frappe/public/js/frappe/views/reports/query_reports.js to have option to freeze column. Please find change below.

3 Likes

Please make a pull request or package this as an app. Great work!

I think frappe team will replace slickgrid with datagrid in v10 once v11 stable.

Will the Datagrid have frozen columns?

Sorry, not datagrid, it’s datatable. It will have in future as this feature in development plan.

https://datatables.net/extensions/fixedcolumns/examples/initialisation/two_columns.html

is this the datatable erpnext is using?

interesting. I never understood why this functionality was not given priority, which should not be very difficult to implement, there are several posts about it.

Many reports are practically unreadable without being able to do this.

3 Likes

Erpnext using this one Frappe DataTable - A simple, modern datatable library for the web . It’s built from scratch by frappe team.

1 Like

@magic-overflow I implemented as per your guide but there is no effect in the columns. I want to freeze particular column while scrolling horizontally in the report.

i have made its worked for Employee Leave Balance Summary report
below code you can check it with DataTable using code pase in you custom app

import DataTable from "frappe-datatable";

class CustomDataTable extends DataTable {
    render() {
        super.render();
        this.bodyScrollable.addEventListener('scroll', (e) => {
            // Your scroll event logic here
            if (this._settingHeaderPosition) return;

            this._settingHeaderPosition = true;

            requestAnimationFrame(() => {
                const left = -e.target.scrollLeft;
                var emt = this.getColumnHeaderElement(0);
                emt.each(function(index, elm) {
                    elm.style.transform = `translateX(${-left}px)`;
                    elm.style.zIndex = '10';

                });
                var emt = this.getColumnHeaderElement(1);
                emt.each(function(index, elm) {
                    elm.style.transform = `translateX(${-left}px)`;
                    elm.style.zIndex = '10';

                });
                var emt = this.getColumnHeaderElement(2);
                emt.each(function(index, elm) {
                    elm.style.transform = `translateX(${-left}px)`;
                    elm.style.zIndex = '10';

                });
                var emt = this.getColumnHeaderElement(3);
                emt.each(function(index, elm) {
                    elm.style.transform = `translateX(${-left}px)`;
                    elm.style.zIndex = '10';

                });
                var tmes = this.get_body_scroll_fixed(0);
                tmes.each(function(index, elm) {
                    elm.style.transform = `translateX(${-left}px)`;
                    elm.style.zIndex = '10';

                });
                var tmes = this.get_body_scroll_fixed(1);
                tmes.each(function(index, elm) {
                    elm.style.transform = `translateX(${-left}px)`;
                    elm.style.zIndex = '10';

                });
                var tmes = this.get_body_scroll_fixed(2);
                tmes.each(function(index, elm) {
                    elm.style.transform = `translateX(${-left}px)`;
                    elm.style.zIndex = '10';

                });
                var tmes = this.get_body_scroll_fixed(3);
                tmes.each(function(index, elm) {
                    elm.style.transform = `translateX(${-left}px)`;
                    elm.style.zIndex = '10';

                });
                this._settingHeaderPosition = false;
            });
        });
    }
    getColumnHeaderElement(colIndex) {
        colIndex = +colIndex;
        if (colIndex < 0) return null;
        return $(`.dt-cell--col-${colIndex}`, this.header);
    }
    get_body_scroll_fixed(colIndex) {
        colIndex = +colIndex;
        if (colIndex < 0) return null;
        return $(`.dt-cell--col-${colIndex}`, this.bodyScrollable);
        this.bodyScrollable
    }
};
frappe.provide("frappe.widget.utils");
frappe.provide("frappe.views");
frappe.provide("frappe.query_reports");
frappe.provide("frappe.views.QueryReport");
const orignal_quert_report = frappe.views.QueryReport.prototype;
frappe.views.QueryReport = class QueryReport extends frappe.views.QueryReport {
    render_datatable() {
        let data = this.data;
        let columns = this.columns.filter((col) => !col.hidden);

        if (this.raw_data.add_total_row && !this.report_settings.tree) {
            data = data.slice();
            data.splice(-1, 1);
        }

        this.$report.show();
        if (
            this.datatable &&
            this.datatable.options &&
            this.datatable.options.showTotalRow === this.raw_data.add_total_row
        ) {
            this.datatable.options.treeView = this.tree_report;
            this.datatable.refresh(data, columns);
        } else {
            let datatable_options = {
                columns: columns,
                data: data,
                inlineFilters: true,
                language: frappe.boot.lang,
                translations: frappe.utils.datatable.get_translations(),
                treeView: this.tree_report,
                layout: "fixed",
                cellHeight: 33,
                showTotalRow: this.raw_data.add_total_row && !this.report_settings.tree,
                direction: frappe.utils.is_rtl() ? "rtl" : "ltr",
                hooks: {
                    columnTotal: frappe.utils.report_column_total,
                },
            };

            if (this.report_settings.get_datatable_options) {
                datatable_options = this.report_settings.get_datatable_options(datatable_options);
            }

            if (this.page_name == "query-report/Employee Leave Balance Summary") {
                this.datatable = new CustomDataTable(this.$report[0], datatable_options);
            } else {
                this.datatable = new DataTable(this.$report[0], datatable_options);
            }
        }

        if (typeof this.report_settings.initial_depth == "number") {
            this.datatable.rowmanager.setTreeDepth(this.report_settings.initial_depth);
        }
        if (this.report_settings.after_datatable_render) {
            this.report_settings.after_datatable_render(this.datatable);
        }
    }
}

thanks all

1 Like