primefaces / primefaces

Ultimate Component Suite for JavaServer Faces
http://www.primefaces.org
MIT License
1.81k stars 763 forks source link

DataExporter: support dynamic grouped columns with ui:repeat #9179

Closed dorkau80 closed 2 years ago

dorkau80 commented 2 years ago

Describe the bug

The showcase page : http://primefaces.org/showcase/ui/data/datatable/group.xhtml?jfwid=f547e shows the issue. After exporting data to XLSX - button on the top right - the data export for second table (Dynamic Columns tab in export) does not show any header for dynamic columns ( 2010 2011 2012 2013 2014 - are missing).

Environment: v.12 ( tested also in v.11 - same issue)

image

Reproducer

No response

Expected behavior

No response

PrimeFaces edition

No response

PrimeFaces version

11.0.0 and up

Theme

No response

JSF implementation

Mojarra

JSF version

2.3

Java version

11

Browser(s)

all

melloware commented 2 years ago

As far as I remember this has never worked and was not designed to work with columns defined this way

              <ui:repeat value="#{dtGroupView.years}" var="year">
                        <p:column headerText="#{year}"/>
                    </ui:repeat>
tandraschko commented 2 years ago

it should be supported if you use #forEachColumn in the exporter but AFAIR we were not sure if we should support this anyway in general

melloware commented 2 years ago

Yep we definitely using table.forEachColumn and it works for p:columns but not for this repeat scenario.

/**
     * Gets and caches the list of UIColumns that are exportable="true", visible="true", and rendered="true".
     * Orders them by displayPriority so they match the UI display of the columns.
     *
     * @param table the Table with columns to export
     * @return the List<UIColumn> that are exportable
     */
    protected List<UIColumn> getExportableColumns(UITable table) {
        if (exportableColumns.containsKey(table)) {
            return exportableColumns.get(table);
        }

        int allColumnsSize = table.getColumns().size();
        List<UIColumn> exportcolumns = new ArrayList<>(allColumnsSize);
        boolean visibleColumnsOnly = getExportConfiguration().isVisibleOnly();
        final AtomicBoolean hasNonDefaultSortPriorities = new AtomicBoolean(false);
        final List<ColumnMeta> visibleColumnMetadata = new ArrayList<>(allColumnsSize);
        Map<String, ColumnMeta> allColumnMeta = table.getColumnMeta();

        table.forEachColumn(true, true, true, column -> {
            if (column.isExportable()) {
                String columnKey = column.getColumnKey();
                ColumnMeta currentMeta = allColumnMeta.get(columnKey);
                if (!visibleColumnsOnly || (visibleColumnsOnly && currentMeta != null && currentMeta.getVisible())) {
                    int displayPriority = column.getDisplayPriority();
                    ColumnMeta metaCopy = new ColumnMeta(columnKey);
                    metaCopy.setDisplayPriority(displayPriority);
                    visibleColumnMetadata.add(metaCopy);
                    if (displayPriority != 0) {
                        hasNonDefaultSortPriorities.set(true);
                    }
                }
            }
            return true;
        });

        if (hasNonDefaultSortPriorities.get()) {
            // sort by display priority
            Comparator<Integer> sortIntegersNaturallyWithNullsLast = Comparator.nullsLast(Comparator.naturalOrder());
            visibleColumnMetadata.sort(Comparator.comparing(ColumnMeta::getDisplayPriority, sortIntegersNaturallyWithNullsLast));
        }

        for (ColumnMeta meta : visibleColumnMetadata) {
            String metaColumnKey = meta.getColumnKey();
            table.invokeOnColumn(metaColumnKey, -1, column -> {
                exportcolumns.add(column);
            });
        }

        exportableColumns.put(table, exportcolumns);

        return exportcolumns;
    }
tandraschko commented 2 years ago

I thought there is a special repeat handling in forEachColumn

melloware commented 2 years ago

I wonder if its because I am also checking Map<String, ColumnMeta> allColumnMeta = table.getColumnMeta(); and those columns are not in the Column Meta? I would have to take a deep dive on it but it doesn't seem critical to me since I never use ui:repeat to create columns I will just leave this in the backlog for now.

tandraschko commented 2 years ago

related ticket: https://github.com/primefaces/primefaces/issues/6409

i think we would need to use #forEachColumn and directly write the excel, not collecting it in a map nor using columnMeta propably

melloware commented 2 years ago

I actually think the issue is in ColumnAware here where it tries to get the Repeating values?

else if (child.getClass().getName().endsWith("UIRepeat")) {
                VisitContext visitContext = VisitContext.createVisitContext(context, null,
                        ComponentUtils.VISIT_HINTS_SKIP_UNRENDERED);
                child.visitTree(visitContext, (ctx, target) -> {
                    if (target.getClass().getName().endsWith("UIRepeat")) {
                        return VisitResult.ACCEPT;
                    }

                    // for now just support basic p:column in ui:repeat
                    if (target instanceof Column) {
                        Column column = (Column) target;
                        if (!callback.test(column)) {
                            return VisitResult.COMPLETE;
                        }
                    }

                    return VisitResult.REJECT;
                });
            }
tandraschko commented 2 years ago

Why should this be an issue? It should work

melloware commented 2 years ago

I am still debugging.

melloware commented 2 years ago

OK I got the bottom of it. It is specific to Excel/PDF trying to render the Grouping because it has to know how many cells. This code was cleaned up from PF Extensions but I should have read closer it has this block in "addColumnGroup`

(https://github.com/primefaces/primefaces/blob/cad398453fecb6099df176d9b43b65fb3f35ac1c/primefaces/src/main/java/org/primefaces/component/datatable/export/DataTableExcelExporter.java#L229-L232)

tandraschko commented 2 years ago

yeah, it need to use #forEachColumn