All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.primefaces.component.datatable.DataTableRenderer Maven / Gradle / Ivy

There is a newer version: 14.0.0
Show newest version
/*
 * The MIT License
 *
 * Copyright (c) 2009-2023 PrimeTek Informatics
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.primefaces.component.datatable;

import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.el.ELContext;
import javax.el.MethodExpression;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.component.UIComponent;
import javax.faces.component.UINamingContainer;
import javax.faces.component.ValueHolder;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

import org.primefaces.component.api.DynamicColumn;
import org.primefaces.component.api.UIColumn;
import org.primefaces.component.celleditor.CellEditor;
import org.primefaces.component.column.Column;
import org.primefaces.component.columngroup.ColumnGroup;
import org.primefaces.component.columns.Columns;
import org.primefaces.component.datatable.feature.DataTableFeature;
import org.primefaces.component.datatable.feature.DataTableFeatures;
import org.primefaces.component.headerrow.HeaderRow;
import org.primefaces.component.row.Row;
import org.primefaces.component.subtable.SubTable;
import org.primefaces.component.summaryrow.SummaryRow;
import org.primefaces.event.data.PostRenderEvent;
import org.primefaces.model.ColumnMeta;
import org.primefaces.model.SortMeta;
import org.primefaces.model.SortOrder;
import org.primefaces.renderkit.DataRenderer;
import org.primefaces.util.*;

public class DataTableRenderer extends DataRenderer {

    private static final Logger LOGGER = Logger.getLogger(DataTableRenderer.class.getName());

    @Override
    public void decode(FacesContext context, UIComponent component) {
        DataTable table = (DataTable) component;

        for (DataTableFeature feature : DataTableFeatures.all()) {
            if (feature.shouldDecode(context, table)) {
                feature.decode(context, table);
            }
        }

        decodeBehaviors(context, component);
    }

    @Override
    public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
        DataTable table = (DataTable) component;

        if (table.shouldEncodeFeature(context)) {
            for (DataTableFeature feature : DataTableFeatures.all()) {
                if (feature.shouldEncode(context, table)) {
                    feature.encode(context, this, table);
                }
            }

            if (table.isFullUpdateRequest(context)) {
                render(context, table);
            }
        }
        else {
            render(context, table);
        }

        context.getApplication().publishEvent(context, PostRenderEvent.class, table);
    }

    protected void render(FacesContext context, DataTable table) throws IOException {
        preRender(context, table);

        encodeMarkup(context, table);
        encodeScript(context, table);

        if (table.isPaginator() && table.getRows() == 0) {
            LOGGER.log(Level.WARNING, "DataTable with paginator=true should also set the rows attribute. ClientId: {0}", table.getClientId());
        }
    }

    protected void preRender(FacesContext context, DataTable table) {
        // trigger init, otherwise column state might be confused when rendering and init at the same time
        table.getSortByAsMap();
        table.getFilterByAsMap();

        if (table.isMultiViewState()) {
            table.restoreMultiViewState();
        }

        if (table.isLiveScroll()) {
            table.setScrollOffset(0);
        }

        if (!table.loadLazyDataIfEnabled()) {
            if (table.isFilteringCurrentlyActive()) {
                DataTableFeatures.filterFeature().filter(context, table);
            }

            if (table.isSortingCurrentlyActive()) {
                DataTableFeatures.sortFeature().sort(context, table);
                table.setRowIndex(-1); // why?
            }
        }

        if (table.isSelectionEnabled()) {
            DataTableFeatures.selectionFeature().decodeSelectionRowKeys(context, table);
        }

        if (table.isPaginator()) {
            table.calculateRows();
            table.calculateFirst();
        }

        table.resetDynamicColumns();
    }

    protected void encodeScript(FacesContext context, DataTable table) throws IOException {
        String selectionMode = table.resolveSelectionMode();
        boolean isFrozenTable = (table.getFrozenColumns() > 0);
        String widgetClass = isFrozenTable ? "FrozenDataTable" : "DataTable";

        if (isFrozenTable && !table.isScrollable()) {
            throw new FacesException("Frozen columns can only be used with a table set scrollable='true'.");
        }

        WidgetBuilder wb = getWidgetBuilder(context);
        wb.init(widgetClass, table);

        //Pagination
        if (table.isPaginator()) {
            encodePaginatorConfig(context, table, wb);
        }

        //Selection
        wb.attr("selectionMode", selectionMode, null)
                .attr("selectionPageOnly", table.isSelectionPageOnly(), true)
                .attr("rowSelectMode", table.getRowSelectMode(), "new")
                .attr("nativeElements", table.isNativeElements(), false)
                .attr("rowSelector", table.getRowSelector(), null)
                .attr("disabledTextSelection", table.isDisabledTextSelection(), true);

        //Filtering
        if (table.isFilteringEnabled()) {
            wb.attr("filter", true)
                    .attr("filterEvent", table.getFilterEvent(), null)
                    .attr("filterDelay", table.getFilterDelay(), Integer.MAX_VALUE);
        }

        //Row expansion
        if (table.getRowExpansion() != null) {
            wb.attr("expansion", true).attr("rowExpandMode", table.getRowExpandMode());
        }

        //Scrolling
        if (table.isScrollable()) {
            wb.attr("scrollable", true)
                    .attr("liveScroll", table.isLiveScroll())
                    .attr("scrollStep", table.getScrollRows())
                    .attr("scrollLimit", table.getRowCount())
                    .attr("scrollWidth", table.getScrollWidth(), null)
                    .attr("scrollHeight", table.getScrollHeight(), null)
                    .attr("frozenColumns", table.getFrozenColumns(), 0)
                    .attr("liveScrollBuffer", table.getLiveScrollBuffer())
                    .attr("virtualScroll", table.isVirtualScroll())
                    .attr("touchable", false,  true);
        }
        else {
            // only allow swipe if not scrollable
            wb.attr("touchable", ComponentUtils.isTouchable(context, table),  true);
        }

        //Resizable/Draggable Columns
        wb.attr("resizableColumns", table.isResizableColumns(), false)
                .attr("liveResize", table.isLiveResize(), false)
                .attr("draggableColumns", table.isDraggableColumns(), false)
                .attr("resizeMode", table.getResizeMode(), "fit");

        //Draggable Rows
        wb.attr("draggableRows", table.isDraggableRows(), false)
                .attr("rowDragSelector", table.getRowDragSelector(), null);

        //Editing
        if (table.isEditable()) {
            wb.attr("editable", true)
                    .attr("editMode", table.getEditMode())
                    .attr("cellSeparator", table.getCellSeparator(), null)
                    .attr("saveOnCellBlur", table.isSaveOnCellBlur(), true)
                    .attr("cellEditMode", table.getCellEditMode(), "eager")
                    .attr("editInitEvent", table.getEditInitEvent())
                    .attr("rowEditMode", table.getRowEditMode(), "eager");
        }

        //Sorting
        if (table.isSortingEnabled()) {
            wb.attr("sorting", true);

            if (table.isMultiSort()) {
                wb.attr("multiSort", true)
                        .nativeAttr("sortMetaOrder", table.getSortMetaAsString(), null);
            }

            if (table.isAllowUnsorting()) {
                wb.attr("allowUnsorting", true);
            }
        }

        if (table.isStickyHeader()) {
            wb.attr("stickyHeader", true)
                    .attr("stickyTopAt", table.getStickyTopAt(), null);
        }

        wb.attr("tabindex", table.getTabindex(), "0")
                .attr("reflow", table.isReflow(), false)
                .attr("rowHover", table.isRowHover(), false)
                .attr("clientCache", table.isClientCache(), false)
                .attr("multiViewState", table.isMultiViewState(), false)
                .attr("partialUpdate", table.isPartialUpdate(), true)
                .nativeAttr("groupColumnIndexes", table.getGroupedColumnIndexes(), null)
                .callback("onRowClick", "function(row)", table.getOnRowClick());

        wb.attr("disableContextMenuIfEmpty", table.isDisableContextMenuIfEmpty());

        //Behaviors
        encodeClientBehaviors(context, table);

        wb.finish();
    }

    protected void encodeMarkup(FacesContext context, DataTable table) throws IOException {
        ResponseWriter writer = context.getResponseWriter();
        String clientId = table.getClientId(context);
        boolean scrollable = table.isScrollable();
        boolean hasPaginator = table.isPaginator();
        boolean resizable = table.isResizableColumns();
        String style = Objects.toString(table.getStyle(), Constants.EMPTY_STRING);
        String paginatorPosition = table.getPaginatorPosition();
        int frozenColumns = table.getFrozenColumns();
        boolean hasFrozenColumns = (frozenColumns != 0);
        String summary = table.getSummary();

        if (table.isReflow()) {
            style = style + ";visibility:hidden;";
        }

        //style class
        String containerClass = getStyleClassBuilder(context)
                .add(DataTable.CONTAINER_CLASS)
                .add(scrollable, DataTable.SCROLLABLE_CONTAINER_CLASS)
                .add(table.getStyleClass())
                .add(resizable, DataTable.RESIZABLE_CONTAINER_CLASS)
                .add(table.isStickyHeader(), DataTable.STICKY_HEADER_CLASS)
                .add(ComponentUtils.isRTL(context, table), DataTable.RTL_CLASS)
                .add(table.isReflow(), DataTable.REFLOW_CLASS)
                .add(hasFrozenColumns, "ui-datatable-frozencolumn")
                .add(table.isStripedRows(), DataTable.STRIPED_ROWS_CLASS)
                .add(table.isShowGridlines(), DataTable.GRIDLINES_CLASS)
                .add("small".equals(table.getSize()), DataTable.SMALL_SIZE_CLASS)
                .add("large".equals(table.getSize()), DataTable.LARGE_SIZE_CLASS)
                .build();

        //aria
        if (summary != null) {
            writer.startElement("span", null);
            writer.writeAttribute("id", clientId + "_summary", null);
            writer.writeAttribute("class", "ui-datatable-summary", null);
            writer.writeText(summary, null);
            writer.endElement("span");
        }

        writer.startElement("div", table);
        writer.writeAttribute("id", clientId, "id");
        writer.writeAttribute("class", containerClass, "styleClass");
        if (LangUtils.isNotBlank(style)) {
            writer.writeAttribute("style", style, "style");
        }

        if (table.isReflow()) {
            encodeSortableHeaderOnReflow(context, table);
        }

        encodeFacet(context, table, table.getHeader(), DataTable.HEADER_CLASS);

        if (hasPaginator && !"bottom".equalsIgnoreCase(paginatorPosition)) {
            encodePaginatorMarkup(context, table, "top");
        }

        if (scrollable) {
            encodeScrollableTable(context, table);
        }
        else {
            encodeRegularTable(context, table);
        }

        if (hasPaginator && !"top".equalsIgnoreCase(paginatorPosition)) {
            encodePaginatorMarkup(context, table, "bottom");
        }

        encodeFacet(context, table, table.getFooter(), DataTable.FOOTER_CLASS);

        if (table.isSelectionEnabled()) {
            encodeStateHolder(context, table, table.getClientId(context) + "_selection", table.getSelectedRowKeysAsString());
        }

        if (table.isDraggableColumns()) {
            encodeStateHolder(context, table, table.getClientId(context) + "_columnOrder", table.getOrderedColumnKeys());
        }

        if (scrollable) {
            encodeStateHolder(context, table, table.getClientId(context) + "_scrollState", table.getScrollState());
        }

        if (resizable) {
            encodeStateHolder(context, table, table.getClientId(context) + "_resizableColumnState", table.getColumnsWidthForClientSide());
        }

        if (table.getRowExpansion() != null) {
            encodeStateHolder(context, table, table.getClientId(context) + "_rowExpansionState", null);
        }

        writer.endElement("div");
    }

    protected void encodeRegularTable(FacesContext context, DataTable table) throws IOException {
        ResponseWriter writer = context.getResponseWriter();

        writer.startElement("div", null);
        writer.writeAttribute("class", DataTable.TABLE_WRAPPER_CLASS, null);

        String tableStyle = table.getTableStyle();

        if (table.isResizableColumns()) {
            String width = table.getWidth();
            if (width != null) {
                if (tableStyle != null) {
                    tableStyle = tableStyle + ";width:" + width + "px";
                }
                else {
                    tableStyle = "width:" + width + "px";
                }
            }
        }

        writer.startElement("table", null);
        writer.writeAttribute("role", "grid", null);
        if (tableStyle != null) {
            writer.writeAttribute("style", tableStyle, null);
        }
        if (table.getTableStyleClass() != null) {
            writer.writeAttribute("class", table.getTableStyleClass(), null);
        }

        String summary = table.getSummary();
        String clientId = table.getClientId(context);

        if (summary != null) {
            writer.writeAttribute("summary", summary, null);
            writer.writeAttribute(HTML.ARIA_DESCRIBEDBY, clientId + "_summary", null);
        }

        encodeThead(context, table);
        encodeTbody(context, table, false);
        encodeTFoot(context, table);

        writer.endElement("table");
        writer.endElement("div");
    }

    protected void encodeScrollableTable(FacesContext context, DataTable table) throws IOException {
        String tableStyle = table.getTableStyle();
        String tableStyleClass = table.getTableStyleClass();
        int frozenColumns = table.getFrozenColumns();
        boolean hasFrozenColumns = (frozenColumns != 0);
        ResponseWriter writer = context.getResponseWriter();
        String clientId = table.getClientId(context);
        int columnsCount = table.getColumns().size();
        boolean isVirtualScroll = table.isVirtualScroll();

        if (hasFrozenColumns) {
            writer.startElement("table", null);
            writer.writeAttribute("class", "ui-datatable-fs", null);
            writer.startElement("tbody", null);
            writer.startElement("tr", null);

            //frozen columns
            writer.startElement("td", null);
            writer.writeAttribute("class", "ui-datatable-frozenlayout-left", null);
            writer.startElement("div", null);
            writer.writeAttribute("class", "ui-datatable-frozen-container", null);
            encodeScrollAreaStart(context, table, DataTable.SCROLLABLE_HEADER_CLASS, DataTable.SCROLLABLE_HEADER_BOX_CLASS, tableStyle, tableStyleClass);
            encodeThead(context, table, 0, frozenColumns, clientId + "_frozenThead", "frozenHeader");
            encodeFrozenRows(context, table, 0, frozenColumns);
            encodeScrollAreaEnd(context);

            if (isVirtualScroll) {
                encodeVirtualScrollBody(context, table, tableStyle, tableStyleClass, 0, frozenColumns, clientId + "_frozenTbody");
            }
            else {
                encodeScrollBody(context, table, tableStyle, tableStyleClass, 0, frozenColumns, clientId + "_frozenTbody");
            }

            encodeScrollAreaStart(context, table, DataTable.SCROLLABLE_FOOTER_CLASS, DataTable.SCROLLABLE_FOOTER_BOX_CLASS, tableStyle, tableStyleClass);
            encodeTFoot(context, table, 0, frozenColumns, clientId + "_frozenTfoot", "frozenFooter");
            encodeScrollAreaEnd(context);
            writer.endElement("div");
            writer.endElement("td");

            //scrollable columns
            writer.startElement("td", null);
            writer.writeAttribute("class", "ui-datatable-frozenlayout-right", null);
            writer.startElement("div", null);
            writer.writeAttribute("class", "ui-datatable-scrollable-container", null);

            encodeScrollAreaStart(context, table, DataTable.SCROLLABLE_HEADER_CLASS, DataTable.SCROLLABLE_HEADER_BOX_CLASS, tableStyle, tableStyleClass);
            encodeThead(context, table, frozenColumns, columnsCount, clientId + "_scrollableThead", "scrollableHeader");
            encodeFrozenRows(context, table, frozenColumns, columnsCount);
            encodeScrollAreaEnd(context);

            if (isVirtualScroll) {
                encodeVirtualScrollBody(context, table, tableStyle, tableStyleClass, frozenColumns, columnsCount, clientId + "_scrollableTbody");
            }
            else {
                encodeScrollBody(context, table, tableStyle, tableStyleClass, frozenColumns, columnsCount, clientId + "_scrollableTbody");
            }

            encodeScrollAreaStart(context, table, DataTable.SCROLLABLE_FOOTER_CLASS, DataTable.SCROLLABLE_FOOTER_BOX_CLASS, tableStyle, tableStyleClass);
            encodeTFoot(context, table, frozenColumns, columnsCount, clientId + "_scrollableTfoot", "scrollableFooter");
            encodeScrollAreaEnd(context);
            writer.endElement("div");
            writer.endElement("td");

            writer.endElement("tr");
            writer.endElement("tbody");
            writer.endElement("table");
        }
        else {
            encodeScrollAreaStart(context, table, DataTable.SCROLLABLE_HEADER_CLASS, DataTable.SCROLLABLE_HEADER_BOX_CLASS, tableStyle, tableStyleClass);
            encodeThead(context, table);
            encodeFrozenRows(context, table, 0, columnsCount);
            encodeScrollAreaEnd(context);

            if (isVirtualScroll) {
                encodeVirtualScrollBody(context, table, tableStyle, tableStyleClass, 0, columnsCount, null);
            }
            else {
                encodeScrollBody(context, table, tableStyle, tableStyleClass, 0, columnsCount, null);
            }

            encodeScrollAreaStart(context, table, DataTable.SCROLLABLE_FOOTER_CLASS, DataTable.SCROLLABLE_FOOTER_BOX_CLASS, tableStyle, tableStyleClass);
            encodeTFoot(context, table);
            encodeScrollAreaEnd(context);
        }
    }

    protected void encodeFrozenScrollableTable(FacesContext context, DataTable table, int frozenColumns) throws IOException {

    }

    protected void encodeScrollAreaStart(FacesContext context, DataTable table, String containerClass, String containerBoxClass,
                                         String tableStyle, String tableStyleClass) throws IOException {

        ResponseWriter writer = context.getResponseWriter();

        writer.startElement("div", null);
        writer.writeAttribute("class", containerClass, null);

        writer.startElement("div", null);
        writer.writeAttribute("class", containerBoxClass, null);

        writer.startElement("table", null);
        writer.writeAttribute("role", "grid", null);
        if (tableStyle != null) {
            writer.writeAttribute("style", tableStyle, null);
        }
        if (tableStyleClass != null) {
            writer.writeAttribute("class", tableStyleClass, null);
        }
    }

    protected void encodeScrollAreaEnd(FacesContext context) throws IOException {
        ResponseWriter writer = context.getResponseWriter();

        writer.endElement("table");
        writer.endElement("div");
        writer.endElement("div");
    }

    protected void encodeScrollBody(FacesContext context, DataTable table, String tableStyle, String tableStyleClass, int columnStart,
                                    int columnEnd, String tbodyId) throws IOException {

        ResponseWriter writer = context.getResponseWriter();
        String scrollHeight = table.getScrollHeight();

        writer.startElement("div", null);
        writer.writeAttribute("class", DataTable.SCROLLABLE_BODY_CLASS, null);
        writer.writeAttribute("tabindex", "-1", null);
        if (LangUtils.isNotBlank(scrollHeight)) {
            if (!endsWithLenghtUnit(scrollHeight)) {
                scrollHeight = scrollHeight + "px";
            }
            // % handle specially in the JS code
            if (scrollHeight.indexOf('%') == -1) {
                writer.writeAttribute("style", "max-height:" + scrollHeight, null);
            }
        }
        writer.startElement("table", null);
        writer.writeAttribute("role", "grid", null);

        if (tableStyle != null) {
            writer.writeAttribute("style", tableStyle, null);
        }
        if (table.getTableStyleClass() != null) {
            writer.writeAttribute("class", tableStyleClass, null);
        }

        encodeTbody(context, table, false, columnStart, columnEnd, tbodyId);

        writer.endElement("table");
        writer.endElement("div");
    }

    protected void encodeVirtualScrollBody(FacesContext context, DataTable table, String tableStyle, String tableStyleClass, int columnStart,
                                           int columnEnd, String tbodyId) throws IOException {

        ResponseWriter writer = context.getResponseWriter();
        String scrollHeight = table.getScrollHeight();
        tableStyleClass = (tableStyleClass == null) ? DataTable.VIRTUALSCROLL_TABLE_CLASS : tableStyleClass + " " + DataTable.VIRTUALSCROLL_TABLE_CLASS;

        writer.startElement("div", null);
        writer.writeAttribute("class", DataTable.SCROLLABLE_BODY_CLASS, null);
        writer.writeAttribute("tabindex", "-1", null);
        if (scrollHeight != null && scrollHeight.indexOf('%') == -1) {
            writer.writeAttribute("style", "max-height:" + scrollHeight + "px", null);
        }

        writer.startElement("div", null);
        writer.writeAttribute("class", DataTable.VIRTUALSCROLL_WRAPPER_CLASS, null);

        writer.startElement("table", null);
        writer.writeAttribute("role", "grid", null);
        writer.writeAttribute("class", tableStyleClass, null);

        if (tableStyle != null) {
            writer.writeAttribute("style", tableStyle, null);
        }

        encodeTbody(context, table, false, columnStart, columnEnd, tbodyId);

        writer.endElement("table");
        writer.endElement("div");

        writer.endElement("div");
    }

    public void encodeColumnHeader(FacesContext context, DataTable table, UIColumn column) throws IOException {
        if (!column.isRendered()) {
            return;
        }

        ColumnMeta columnMeta = table.getColumnMeta().get(column.getColumnKey());

        ResponseWriter writer = context.getResponseWriter();
        String clientId = column.getContainerClientId(context);

        boolean sortable = table.isColumnSortable(context, column);
        boolean filterable = table.isColumnFilterable(context, column);
        boolean isGroupedColumn = column.isGroupRow();
        String selectionMode = column.getSelectionMode();
        SortMeta sortMeta = null;
        boolean resizable = table.isResizableColumns() && column.isResizable();
        boolean draggable = table.isDraggableColumns() && column.isDraggable();
        int responsivePriority = column.getResponsivePriority();

        boolean columnVisible = column.isVisible();
        if (columnMeta != null && columnMeta.getVisible() != null) {
            columnVisible = columnMeta.getVisible();
        }

        String columnClass = getStyleClassBuilder(context)
                .add(DataTable.COLUMN_HEADER_CLASS)
                .add(sortable, DataTable.SORTABLE_COLUMN_CLASS)
                .add(filterable, DataTable.FILTER_COLUMN_CLASS)
                .add(selectionMode != null, DataTable.SELECTION_COLUMN_CLASS)
                .add(isGroupedColumn, DataTable.GROUPED_COLUMN_CLASS)
                .add(resizable,  DataTable.RESIZABLE_COLUMN_CLASS)
                .add(draggable, DataTable.DRAGGABLE_COLUMN_CLASS)
                .add(!column.isToggleable(), DataTable.STATIC_COLUMN_CLASS)
                .add(!columnVisible, DataTable.HIDDEN_COLUMN_CLASS)
                .add(column.getStyleClass())
                .add(responsivePriority > 0, "ui-column-p-" + responsivePriority)
                .build();

        if (sortable) {
            sortMeta = table.getSortByAsMap().get(column.getColumnKey());
            if (sortMeta.isActive()) {
                columnClass += " ui-state-active";
            }
        }

        String style = column.getStyle();
        String width = column.getWidth();

        if (columnMeta != null && columnMeta.getWidth() != null) {
            width = columnMeta.getWidth();
        }

        if (width != null) {
            String unit = endsWithLenghtUnit(width) ? Constants.EMPTY_STRING : "px";
            if (style != null) {
                style = style + ";width:" + width + unit;
            }
            else {
                style = "width:" + width + unit;
            }
        }

        String ariaHeaderLabel = getHeaderLabel(context, column);
        UIComponent component = (column instanceof UIComponent) ? (UIComponent) column : null;

        writer.startElement("th", component);
        writer.writeAttribute("id", clientId, null);
        writer.writeAttribute("class", columnClass, null);
        writer.writeAttribute(HTML.ARIA_LABEL, ariaHeaderLabel, null);
        writer.writeAttribute("scope", "col", null);
        if (component != null) {
            renderDynamicPassThruAttributes(context, component);
        }
        if (style != null) {
            writer.writeAttribute("style", style, null);
        }
        if (column.getRowspan() != 1) {
            writer.writeAttribute("rowspan", column.getRowspan(), null);
        }
        if (column.getColspan() != 1) {
            writer.writeAttribute("colspan", column.getColspan(), null);
        }

        if (filterable) {
            String filterPosition = column.getFilterPosition();

            if ("bottom".equals(filterPosition)) {
                encodeColumnHeaderContent(context, table, column, sortMeta);
                encodeFilter(context, table, column);
            }
            else if ("top".equals(filterPosition)) {
                encodeFilter(context, table, column);
                encodeColumnHeaderContent(context, table, column, sortMeta);
            }
            else {
                throw new FacesException(filterPosition + " is an invalid option for filterPosition, valid values are 'bottom' or 'top'.");
            }
        }
        else {
            encodeColumnHeaderContent(context, table, column, sortMeta);
        }

        if (selectionMode != null && "multiple".equalsIgnoreCase(selectionMode) && table.isShowSelectAll()) {
            encodeCheckbox(context, table, table.isSelectAll(), false, HTML.CHECKBOX_ALL_CLASS, true);
        }

        writer.endElement("th");
    }

    protected String resolveDefaultSortIcon(SortMeta sortMeta) {
        SortOrder sortOrder = sortMeta.getOrder();
        String sortIcon = DataTable.SORTABLE_COLUMN_ICON_CLASS;
        if (sortOrder.isAscending()) {
            sortIcon = DataTable.SORTABLE_COLUMN_ASCENDING_ICON_CLASS;
        }
        else if (sortOrder.isDescending()) {
            sortIcon = DataTable.SORTABLE_COLUMN_DESCENDING_ICON_CLASS;
        }

        return sortIcon;
    }

    protected void encodeColumnHeaderContent(FacesContext context, DataTable table, UIColumn column,
                SortMeta sortMeta) throws IOException {
        ResponseWriter writer = context.getResponseWriter();

        UIComponent header = column.getFacet("header");
        String headerText = column.getHeaderText();

        writer.startElement("span", null);
        writer.writeAttribute("class", DataTable.COLUMN_TITLE_CLASS, null);

        if (ComponentUtils.shouldRenderFacet(header, table.isRenderEmptyFacets())) {
            header.encodeAll(context);
        }
        else if (headerText != null) {
            if (table.isEscapeText()) {
                writer.writeText(headerText, "headerText");
            }
            else {
                writer.write(headerText);
            }
        }

        writer.endElement("span");

        if (sortMeta != null) {
            String sortIcon = resolveDefaultSortIcon(sortMeta);
            if (sortIcon != null) {
                writer.startElement("span", null);
                writer.writeAttribute("class", sortIcon, null);
                writer.endElement("span");

                if (table.isMultiSort()) {
                    writer.startElement("span", null);
                    writer.writeAttribute("class", DataTable.SORTABLE_PRIORITY_CLASS, null);
                    writer.endElement("span");
                }
            }
        }
    }

    protected void encodeFilter(FacesContext context, DataTable table, UIColumn column) throws IOException {
        if (table.isGlobalFilterOnly()) {
            return;
        }

        ResponseWriter writer = context.getResponseWriter();
        UIComponent filterFacet = column.getFilterComponent();

        if (!ComponentUtils.shouldRenderFacet(filterFacet, table.isRenderEmptyFacets())) {
            encodeDefaultFilter(context, table, column, writer);
        }
        else {
            Object filterValue = table.getFilterValue(column);
            ((ValueHolder) filterFacet).setValue(filterValue);

            writer.startElement("div", null);
            writer.writeAttribute("class", DataTable.COLUMN_CUSTOM_FILTER_CLASS, null);
            filterFacet.encodeAll(context);
            writer.endElement("div");
        }
    }

    protected void encodeDefaultFilter(FacesContext context, DataTable table, UIColumn column,
        ResponseWriter writer) throws IOException {
        String separator = String.valueOf(UINamingContainer.getSeparatorChar(context));
        boolean disableTabbing = table.getScrollWidth() != null;
        String filterId = column.getContainerClientId(context) + separator + "filter";
        Object filterValue = findFilterValueForColumn(context, table, column, filterId);
        String filterStyleClass = column.getFilterStyleClass();

        //aria
        String ariaLabelId = filterId + "_label";
        String ariaHeaderLabel = getHeaderLabel(context, column);

        String ariaMessage = MessageFactory.getMessage(DataTable.ARIA_FILTER_BY, ariaHeaderLabel);

        writer.startElement("label", null);
        writer.writeAttribute("id", ariaLabelId, null);
        writer.writeAttribute("for", filterId, null);
        writer.writeAttribute("class", "ui-helper-hidden", null);
        writer.writeText(ariaMessage, null);
        writer.endElement("label");

        encodeFilterInput(column, writer, disableTabbing, filterId, filterStyleClass, filterValue, ariaLabelId);
    }

    protected void encodeFilterInput(UIColumn column, ResponseWriter writer, boolean disableTabbing,
        String filterId, String filterStyleClass, Object filterValue, String ariaLabelId) throws IOException {

        filterStyleClass = filterStyleClass == null
                           ? DataTable.COLUMN_INPUT_FILTER_CLASS
                           : DataTable.COLUMN_INPUT_FILTER_CLASS + " " + filterStyleClass;

        writer.startElement("input", null);
        writer.writeAttribute("id", filterId, null);
        writer.writeAttribute("name", filterId, null);
        writer.writeAttribute("class", filterStyleClass, null);
        writer.writeAttribute("value", filterValue, null);
        writer.writeAttribute("autocomplete", "off", null);
        writer.writeAttribute(HTML.ARIA_LABELLEDBY, ariaLabelId, null);

        if (disableTabbing) {
            writer.writeAttribute("tabindex", "-1", null);
        }

        if (column.getFilterStyle() != null) {
            writer.writeAttribute("style", column.getFilterStyle(), null);
        }

        if (column.getFilterMaxLength() != Integer.MAX_VALUE) {
            writer.writeAttribute("maxlength", column.getFilterMaxLength(), null);
        }

        writer.endElement("input");
    }

    protected Object findFilterValueForColumn(FacesContext context, DataTable table,
        UIColumn column, String filterId) {

        Object filterValue;
        if (table.isReset()) {
            filterValue = Constants.EMPTY_STRING;
        }
        else {
            filterValue = table.getFilterValue(column);
            if (filterValue == null) {
                Map params = context.getExternalContext().getRequestParameterMap();
                if (params.containsKey(filterId)) {
                    filterValue = params.get(filterId);
                }
                else {
                    Object columnFilterValue = column.getFilterValue();
                    filterValue = (columnFilterValue == null) ? Constants.EMPTY_STRING : columnFilterValue.toString();
                }
            }
        }
        return filterValue;
    }

    public void encodeColumnFooter(FacesContext context, DataTable table, UIColumn column) throws IOException {
        if (!column.isRendered()) {
            return;
        }

        ColumnMeta columnMeta = table.getColumnMeta().get(column.getColumnKey());

        ResponseWriter writer = context.getResponseWriter();

        int responsivePriority = column.getResponsivePriority();
        String style = column.getStyle();
        String styleClass = column.getStyleClass();
        styleClass = styleClass == null ? DataTable.COLUMN_FOOTER_CLASS : DataTable.COLUMN_FOOTER_CLASS + " " + styleClass;

        boolean columnVisible = column.isVisible();
        if (columnMeta != null && columnMeta.getVisible() != null) {
            columnVisible = columnMeta.getVisible();
        }

        if (!columnVisible) {
            styleClass = styleClass + " " + DataTable.HIDDEN_COLUMN_CLASS;
        }

        if (responsivePriority > 0) {
            styleClass = styleClass + " ui-column-p-" + responsivePriority;
        }

        writer.startElement("td", null);
        writer.writeAttribute("class", styleClass, null);

        if (style != null) {
            writer.writeAttribute("style", style, null);
        }
        if (column.getRowspan() != 1) {
            writer.writeAttribute("rowspan", column.getRowspan(), null);
        }
        if (column.getColspan() != 1) {
            writer.writeAttribute("colspan", column.getColspan(), null);
        }

        //Footer content
        UIComponent facet = column.getFacet("footer");
        String text = column.getFooterText();
        if (ComponentUtils.shouldRenderFacet(facet, table.isRenderEmptyFacets())) {
            facet.encodeAll(context);
        }
        else if (text != null) {
            if (table.isEscapeText()) {
                writer.writeText(text, "footerText");
            }
            else {
                writer.write(text);
            }
        }

        writer.endElement("td");
    }

    protected void encodeThead(FacesContext context, DataTable table) throws IOException {
        encodeThead(context, table, 0, table.getColumns().size(), null, null);
    }

    protected void encodeThead(FacesContext context, DataTable table, int columnStart, int columnEnd, String theadId,
                               String columnGroupType) throws IOException {

        ResponseWriter writer = context.getResponseWriter();
        List columns = table.getColumns();
        String theadClientId = (theadId == null) ? table.getClientId(context) + "_head" : theadId;
        String colGroupType = (columnGroupType == null) ? "header" : columnGroupType;
        ColumnGroup group = table.getColumnGroup(colGroupType);

        writer.startElement("thead", null);
        writer.writeAttribute("id", theadClientId, null);

        if (group != null && group.isRendered()) {
            context.getAttributes().put(Constants.HELPER_RENDERER, "columnGroup");

            for (UIComponent child : group.getChildren()) {
                if (child.isRendered()) {
                    if (child instanceof Row) {
                        Row headerRow = (Row) child;
                        String rowClass = headerRow.getStyleClass();
                        String rowStyle = headerRow.getStyle();

                        writer.startElement("tr", null);
                        if (rowClass != null) {
                            writer.writeAttribute("class", rowClass, null);
                        }
                        if (rowStyle != null) {
                            writer.writeAttribute("style", rowStyle, null);
                        }

                        for (UIComponent headerRowChild : headerRow.getChildren()) {
                            if (headerRowChild.isRendered()) {
                                if (headerRowChild instanceof Column) {
                                    encodeColumnHeader(context, table, (Column) headerRowChild);
                                }
                                else if (headerRowChild instanceof Columns) {
                                    List dynamicColumns = ((Columns) headerRowChild).getDynamicColumns();
                                    for (DynamicColumn dynaColumn : dynamicColumns) {
                                        dynaColumn.applyModel();
                                        encodeColumnHeader(context, table, dynaColumn);
                                    }
                                }
                                else {
                                    headerRowChild.encodeAll(context);
                                }
                            }
                        }

                        writer.endElement("tr");
                    }
                    else {
                        child.encodeAll(context);
                    }
                }
            }

            context.getAttributes().remove(Constants.HELPER_RENDERER);
        }
        else {
            writer.startElement("tr", null);

            for (int i = columnStart; i < columnEnd; i++) {
                UIColumn column = columns.get(i);
                if (column instanceof DynamicColumn) {
                    ((DynamicColumn) column).applyModel();
                }
                encodeColumnHeader(context, table, column);
            }

            writer.endElement("tr");
        }

        writer.endElement("thead");
    }

    public void encodeTbody(FacesContext context, DataTable table, boolean dataOnly) throws IOException {
        encodeTbody(context, table, dataOnly, 0, table.getColumns().size(), null);
    }

    public void encodeTbody(FacesContext context, DataTable table, boolean dataOnly, int columnStart, int columnEnd, String tbodyId)
            throws IOException {

        ResponseWriter writer = context.getResponseWriter();
        String rowIndexVar = table.getRowIndexVar();
        String clientId = table.getClientId(context);
        String emptyMessage = table.getEmptyMessage();
        UIComponent emptyFacet = table.getFacet("emptyMessage");
        SubTable subTable = table.getSubTable();
        String tbodyClientId = (tbodyId == null) ? clientId + "_data" : tbodyId;
        Map params = context.getExternalContext().getRequestParameterMap();

        int rows = table.getRows();
        int first = table.isClientCacheRequest(context) ? Integer.valueOf(params.get(clientId + "_first")) + rows : table.getFirst();
        int rowCount = table.getRowCount();
        int rowCountToRender = rows == 0 ? (table.isLiveScroll() ? (table.getScrollRows() + table.getScrollOffset()) : rowCount) : rows;

        if (table.isVirtualScroll()) {
            int virtualScrollRowCount = (table.getScrollRows() * 2);
            rowCountToRender = virtualScrollRowCount > rowCount ? rowCount : virtualScrollRowCount;
        }

        int frozenRows = table.getFrozenRows();
        boolean hasData = rowCount > 0;

        if (first == 0 && frozenRows > 0) {
            first += frozenRows;
        }

        if (!dataOnly) {
            writer.startElement("tbody", null);
            writer.writeAttribute("id", tbodyClientId, null);
            writer.writeAttribute("class", DataTable.DATA_CLASS, null);

            if (table.isRowSelectionEnabled()) {
                writer.writeAttribute("tabindex", table.getTabindex(), null);
            }
        }

        if (hasData) {
            if (subTable != null) {
                encodeSubTable(context, table, subTable, first, (first + rowCountToRender));
            }
            else {
                encodeRows(context, table, first, (first + rowCountToRender), columnStart, columnEnd);
            }
        }
        else {
            //Empty message
            writer.startElement("tr", null);
            writer.writeAttribute("class", DataTable.EMPTY_MESSAGE_ROW_CLASS, null);

            writer.startElement("td", null);
            writer.writeAttribute("colspan", table.getColumnsCountWithSpan(), null);

            if (ComponentUtils.shouldRenderFacet(emptyFacet, table.isRenderEmptyFacets())) {
                emptyFacet.encodeAll(context);
            }
            else {
                writer.writeText(emptyMessage, "emptyMessage");
            }

            writer.endElement("td");

            writer.endElement("tr");
        }

        if (!dataOnly) {
            writer.endElement("tbody");
        }

        //Cleanup
        table.setRowIndex(-1);
        if (rowIndexVar != null) {
            context.getExternalContext().getRequestMap().remove(rowIndexVar);
        }
    }

    protected void encodeRows(FacesContext context, DataTable table, int first, int last, int columnStart, int columnEnd) throws IOException {
        String clientId = table.getClientId(context);
        List summaryRows = table.getSummaryRows();
        HeaderRow headerRow = table.getHeaderRow();
        ELContext elContext = context.getELContext();

        SortMeta sort = table.getHighestPriorityActiveSortMeta();
        boolean encodeHeaderRow = headerRow != null && headerRow.isEnabled() && sort != null;
        boolean encodeSummaryRow = (!summaryRows.isEmpty() && sort != null);

        for (int i = first; i < last; i++) {
            table.resetDynamicColumns();

            table.setRowIndex(i);
            if (!table.isRowAvailable()) {
                break;
            }

            table.setRowIndex(i);

            if (encodeHeaderRow && (i == first || !isInSameGroup(context, table, i, -1, sort.getSortBy(), elContext))) {
                table.setRowIndex(i);
                encodeHeaderRow(context, table, headerRow);
            }

            table.setRowIndex(i);
            encodeRow(context, table, clientId, i, columnStart, columnEnd);

            if (encodeSummaryRow && !isInSameGroup(context, table, i, 1, sort.getSortBy(), elContext)) {
                table.setRowIndex(i);
                encodeSummaryRow(context, summaryRows, sort);
            }
        }
    }

    protected void encodeFrozenRows(FacesContext context, DataTable table, int columnStart, int columnEnd) throws IOException {
        int frozenRows = table.getFrozenRows();
        if (frozenRows == 0) {
            return;
        }

        ResponseWriter writer = context.getResponseWriter();
        String clientId = table.getClientId(context);

        writer.startElement("tbody", null);
        writer.writeAttribute("class", DataTable.DATA_CLASS, null);

        for (int i = 0; i < frozenRows; i++) {
            table.setRowIndex(i);
            encodeRow(context, table, clientId, i, columnStart, columnEnd);
        }

        writer.endElement("tbody");
    }

    protected void encodeSummaryRow(FacesContext context, List summaryRows, SortMeta sort) throws IOException {
        for (int i = 0; i < summaryRows.size(); i++) {
            SummaryRow summaryRow = summaryRows.get(i);
            MethodExpression me = summaryRow.getListener();
            if (me != null) {
                me.invoke(context.getELContext(), new Object[]{sort.getSortBy()});
            }

            summaryRow.encodeAll(context);
        }
    }

    protected void encodeHeaderRow(FacesContext context, DataTable table, HeaderRow headerRow) throws IOException {
        headerRow.encodeAll(context);
    }

    public boolean encodeRow(FacesContext context, DataTable table, String clientId, int rowIndex) throws IOException {
        return encodeRow(context, table, clientId, rowIndex, 0, table.getColumns().size());
    }

    public boolean encodeRow(FacesContext context, DataTable table, String clientId, int rowIndex, int columnStart, int columnEnd)
            throws IOException {

        ResponseWriter writer = context.getResponseWriter();
        boolean selectionEnabled = table.isSelectionEnabled();
        boolean rowExpansionAvailable = table.getRowExpansion() != null;
        String rowKey = null;
        List columns = table.getColumns();
        HeaderRow headerRow = table.getHeaderRow();

        if (selectionEnabled || rowExpansionAvailable) {
            rowKey = table.getRowKey(table.getRowData());
        }

        //Preselection
        boolean selected = selectionEnabled && table.getSelectedRowKeys().contains(rowKey);
        boolean disabled = table.isDisabledSelection();
        boolean allowSelection = selectionEnabled && !disabled;
        boolean expanded = table.isExpandedRow() || (rowExpansionAvailable && table.getExpandedRowKeys().contains(rowKey));

        String rowStyleClass = getStyleClassBuilder(context)
                .add(DataTable.ROW_CLASS)
                .add(rowIndex % 2 == 0, DataTable.EVEN_ROW_CLASS, DataTable.ODD_ROW_CLASS)
                .add(allowSelection, DataTable.SELECTABLE_ROW_CLASS)
                .add(selected, "ui-state-highlight")
                .add(table.isEditingRow(),  DataTable.EDITING_ROW_CLASS)
                .add(table.getRowStyleClass())
                .add(expanded, DataTable.EXPANDED_ROW_CLASS)
                .build();

        writer.startElement("tr", null);
        writer.writeAttribute("data-ri", rowIndex, null);
        if (rowKey != null) {
            writer.writeAttribute("data-rk", rowKey, null);
        }
        writer.writeAttribute("class", rowStyleClass, null);
        writer.writeAttribute("title", table.getRowTitle(), null);
        if (selectionEnabled) {
            writer.writeAttribute(HTML.ARIA_SELECTED, String.valueOf(selected), null);
        }
        if (headerRow != null && !headerRow.isExpanded()) {
            writer.writeAttribute("style", "display: none;", null);
        }

        for (int i = columnStart; i < columnEnd; i++) {
            UIColumn column = columns.get(i);

            if (column instanceof Column) {
                encodeCell(context, table, column, selected, allowSelection, rowIndex);
            }
            else if (column instanceof DynamicColumn) {
                DynamicColumn dynamicColumn = (DynamicColumn) column;
                dynamicColumn.applyModel();

                encodeCell(context, table, dynamicColumn, false, allowSelection, rowIndex);
            }
        }

        writer.endElement("tr");

        if (expanded) {
            DataTableFeatures.rowExpandFeature().encodeExpansion(context, this, table, rowIndex);
        }

        return true;
    }

    protected void encodeCell(FacesContext context, DataTable table, UIColumn column, boolean selected,
            boolean rowSelectionEnabled, int rowIndex) throws IOException {
        if (!column.isRendered()) {
            return;
        }

        ColumnMeta columnMeta = table.getColumnMeta().get(column.getColumnKey(table, rowIndex));

        boolean columnVisible = column.isVisible();
        if (columnMeta != null && columnMeta.getVisible() != null) {
            columnVisible = columnMeta.getVisible();
        }

        ResponseWriter writer = context.getResponseWriter();
        boolean columnSelectionEnabled = column.getSelectionMode() != null;
        boolean isGroupedColumn = column.isGroupRow();
        CellEditor editor = column.getCellEditor();
        boolean editorEnabled = editor != null && editor.isRendered();
        int responsivePriority = column.getResponsivePriority();
        String title = column.getTitle();
        String style = column.getStyle();

        String styleClass = getStyleClassBuilder(context)
                .add(columnSelectionEnabled, DataTable.SELECTION_COLUMN_CLASS)
                .add(isGroupedColumn, DataTable.GROUPED_COLUMN_CLASS)
                .add(editorEnabled && editor.isDisabled(), DataTable.CELL_EDITOR_DISABLED_CLASS)
                .add(editorEnabled && !editor.isDisabled(), DataTable.EDITABLE_COLUMN_CLASS)
                .add(!column.isSelectRow(), DataTable.UNSELECTABLE_COLUMN_CLASS)
                .add(!columnVisible, DataTable.HIDDEN_COLUMN_CLASS)
                .add(column.getStyleClass())
                .add(responsivePriority > 0, "ui-column-p-" + responsivePriority)
                .build();

        int colspan = column.getColspan();
        int rowspan = column.getRowspan();

        writer.startElement("td", null);
        writer.writeAttribute("role", "gridcell", null);
        if (colspan != 1) {
            writer.writeAttribute("colspan", colspan, null);
        }
        if (rowspan != 1) {
            writer.writeAttribute("rowspan", rowspan, null);
        }
        if (style != null) {
            writer.writeAttribute("style", style, null);
        }
        if (styleClass != null) {
            writer.writeAttribute("class", styleClass, null);
        }
        if (title != null) {
            writer.writeAttribute("title", title, null);
        }
        UIComponent component = (column instanceof UIComponent) ? (UIComponent) column : null;
        if (component != null) {
            renderDynamicPassThruAttributes(context, component);
        }

        if (columnSelectionEnabled) {
            encodeColumnSelection(context, table, column, selected, rowSelectionEnabled);
        }

        if (hasColumnDefaultRendering(table, column)) {
            encodeDefaultFieldCell(context, table, column, writer);
        }
        else if (column instanceof DynamicColumn) {
            encodeDynamicCell(context, table, column);
        }
        else {
            column.renderChildren(context);
        }

        writer.endElement("td");
    }

    /**
     * Encodes dynamic column. Allows to override default behavior.
     */
    protected void encodeDynamicCell(FacesContext context, DataTable table, UIColumn column) throws IOException {
        column.encodeAll(context);
    }

    protected void encodeDefaultFieldCell(FacesContext context, DataTable table, UIColumn column, ResponseWriter writer) throws IOException {
        Object value = table.getConvertedFieldValue(context, column);
        if (value != null) {
            writer.writeText(value, null);
        }
    }

    protected void encodeTFoot(FacesContext context, DataTable table) throws IOException {
        encodeTFoot(context, table, 0, table.getColumns().size(), null, null);
    }

    protected void encodeTFoot(FacesContext context, DataTable table, int columnStart, int columnEnd, String tfootId, String columnGroupType)
            throws IOException {

        ResponseWriter writer = context.getResponseWriter();
        List columns = table.getColumns();
        String tfootClientId = (tfootId == null) ? table.getClientId(context) + "_foot" : tfootId;
        String colGroupType = (columnGroupType == null) ? "footer" : columnGroupType;
        ColumnGroup group = table.getColumnGroup(colGroupType);
        boolean hasFooterColumn = table.hasFooterColumn();
        boolean shouldRenderFooter = (hasFooterColumn || group != null);

        if (!shouldRenderFooter) {
            return;
        }

        writer.startElement("tfoot", null);
        writer.writeAttribute("id", tfootClientId, null);

        if (group != null && group.isRendered()) {
            context.getAttributes().put(Constants.HELPER_RENDERER, "columnGroup");

            for (UIComponent child : group.getChildren()) {
                if (child.isRendered()) {
                    if (child instanceof Row) {
                        Row footerRow = (Row) child;
                        String rowClass = footerRow.getStyleClass();
                        String rowStyle = footerRow.getStyle();

                        writer.startElement("tr", null);
                        if (rowClass != null) {
                            writer.writeAttribute("class", rowClass, null);
                        }
                        if (rowStyle != null) {
                            writer.writeAttribute("style", rowStyle, null);
                        }

                        for (UIComponent footerRowChild : footerRow.getChildren()) {
                            if (footerRowChild.isRendered()) {
                                if (footerRowChild instanceof Column) {
                                    encodeColumnFooter(context, table, (Column) footerRowChild);
                                }
                                else if (footerRowChild instanceof Columns) {
                                    List dynamicColumns = ((Columns) footerRowChild).getDynamicColumns();
                                    for (DynamicColumn dynaColumn : dynamicColumns) {
                                        dynaColumn.applyModel();
                                        encodeColumnFooter(context, table, dynaColumn);
                                    }
                                }
                                else {
                                    footerRowChild.encodeAll(context);
                                }
                            }
                        }
                        writer.endElement("tr");
                    }
                    else {
                        child.encodeAll(context);
                    }
                }
            }

            context.getAttributes().remove(Constants.HELPER_RENDERER);
        }
        else if (table.hasFooterColumn()) {
            writer.startElement("tr", null);

            for (int i = columnStart; i < columnEnd; i++) {
                UIColumn column = columns.get(i);

                if (column instanceof Column) {
                    encodeColumnFooter(context, table, column);
                }
                else if (column instanceof DynamicColumn) {
                    DynamicColumn dynamicColumn = (DynamicColumn) column;
                    dynamicColumn.applyModel();

                    encodeColumnFooter(context, table, dynamicColumn);
                }
            }

            writer.endElement("tr");
        }

        writer.endElement("tfoot");
    }

    protected void encodeFacet(FacesContext context, DataTable table, UIComponent facet, String styleClass) throws IOException {
        if (facet == null) {
            return;
        }

        if (!ComponentUtils.shouldRenderFacet(facet, table.isRenderEmptyFacets())) {
            return;
        }
        ResponseWriter writer = context.getResponseWriter();

        writer.startElement("div", null);
        writer.writeAttribute("class", styleClass, null);

        facet.encodeAll(context);

        writer.endElement("div");
    }

    protected void encodeStateHolder(FacesContext context, DataTable table, String id, String value) throws IOException {
        renderHiddenInput(context, id, value, false);
    }

    @Override
    public void encodeChildren(FacesContext context, UIComponent component) throws IOException {
        //Rendering happens on encodeEnd
    }

    @Override
    public boolean getRendersChildren() {
        return true;
    }

    protected void encodeRadio(FacesContext context, DataTable table, boolean checked, boolean disabled) throws IOException {
        ResponseWriter writer = context.getResponseWriter();

        if (table.isNativeElements()) {
            encodeNativeRadio(context, table, checked, disabled);
        }
        else {
            String iconClass = checked ? HTML.RADIOBUTTON_CHECKED_ICON_CLASS : HTML.RADIOBUTTON_UNCHECKED_ICON_CLASS;
            String boxClass = getStyleClassBuilder(context)
                        .add(HTML.RADIOBUTTON_BOX_CLASS)
                        .add(disabled, "ui-state-disabled")
                        .add(checked, "ui-state-active")
                        .build();

            writer.startElement("div", null);
            writer.writeAttribute("class", HTML.RADIOBUTTON_CLASS, null);

            writer.startElement("div", null);
            writer.writeAttribute("class", "ui-helper-hidden-accessible", null);
            encodeNativeRadio(context, table, checked, disabled);
            writer.endElement("div");

            writer.startElement("div", null);
            writer.writeAttribute("class", boxClass, null);

            writer.startElement("span", null);
            writer.writeAttribute("class", iconClass, null);
            writer.endElement("span");

            writer.endElement("div");
            writer.endElement("div");
        }
    }

    protected void encodeColumnSelection(FacesContext context, DataTable table, UIColumn column, boolean selected, boolean rowSelectionEnabled)
            throws IOException {

        String selectionMode = column.getSelectionMode();

        if ("single".equalsIgnoreCase(selectionMode)) {
            encodeRadio(context, table, selected, !rowSelectionEnabled);
        }
        else if ("multiple".equalsIgnoreCase(selectionMode)) {
            encodeCheckbox(context, table, selected, !rowSelectionEnabled, HTML.CHECKBOX_CLASS, false);
        }
        else {
            throw new FacesException("Invalid column selection mode:" + selectionMode);
        }
    }

    protected void encodeCheckbox(FacesContext context, DataTable table, boolean checked, boolean disabled, String styleClass,
                                  boolean isHeaderCheckbox) throws IOException {

        ResponseWriter writer = context.getResponseWriter();

        if (table.isNativeElements()) {
            encodeNativeCheckbox(context, table, checked, disabled, isHeaderCheckbox);
        }
        else {
            String ariaRowLabel = table.getAriaRowLabel();
            Object rowKey = null;
            String boxClass = getStyleClassBuilder(context)
                        .add(HTML.CHECKBOX_BOX_CLASS)
                        .add(disabled, "ui-state-disabled")
                        .add(checked, "ui-state-active")
                        .build();
            String iconClass = checked ? HTML.CHECKBOX_CHECKED_ICON_CLASS : HTML.CHECKBOX_UNCHECKED_ICON_CLASS;

            if (isHeaderCheckbox) {
                rowKey = "head";
                ariaRowLabel = MessageFactory.getMessage(DataTable.ARIA_HEADER_CHECKBOX_ALL);
            }
            else {
                rowKey = table.getRowKey();
            }

            writer.startElement("div", null);
            writer.writeAttribute("class", styleClass, "styleClass");

            writer.startElement("div", null);

            writer.writeAttribute("id", table.getClientId(context) + "_" + rowKey + "_checkbox", null);
            writer.writeAttribute("role", "checkbox", null);
            writer.writeAttribute("tabindex", "0", null);
            writer.writeAttribute(HTML.ARIA_LABEL, ariaRowLabel, null);
            writer.writeAttribute(HTML.ARIA_CHECKED, String.valueOf(checked), null);

            if (disabled) {
                writer.writeAttribute("aria-disabled", "true", null);
            }

            writer.writeAttribute("class", boxClass, null);

            writer.startElement("span", null);
            writer.writeAttribute("class", iconClass, null);
            writer.endElement("span");

            writer.endElement("div");
            writer.endElement("div");
        }
    }

    protected void encodeNativeCheckbox(FacesContext context, DataTable table, boolean checked, boolean disabled,
                                        boolean isHeaderCheckbox) throws IOException {

        ResponseWriter writer = context.getResponseWriter();

        String ariaRowLabel = table.getAriaRowLabel();
        if (isHeaderCheckbox) {
            ariaRowLabel = MessageFactory.getMessage(DataTable.ARIA_HEADER_CHECKBOX_ALL);
        }

        writer.startElement("input", null);
        writer.writeAttribute("type", "checkbox", null);
        writer.writeAttribute("name", table.getClientId(context) + "_checkbox", null);
        writer.writeAttribute(HTML.ARIA_LABEL, ariaRowLabel, null);
        writer.writeAttribute(HTML.ARIA_CHECKED, String.valueOf(checked), null);

        if (checked) {
            writer.writeAttribute("checked", "checked", null);
        }

        if (disabled) {
            writer.writeAttribute("disabled", "disabled", null);
        }

        writer.endElement("input");
    }

    protected void encodeNativeRadio(FacesContext context, DataTable table, boolean checked, boolean disabled) throws IOException {
        ResponseWriter writer = context.getResponseWriter();

        String ariaRowLabel = table.getAriaRowLabel();

        writer.startElement("input", null);
        writer.writeAttribute("type", "radio", null);
        writer.writeAttribute("name", table.getClientId(context) + "_radio", null);
        writer.writeAttribute(HTML.ARIA_LABEL, ariaRowLabel, null);

        if (checked) {
            writer.writeAttribute("checked", "checked", null);
        }

        if (disabled) {
            writer.writeAttribute("disabled", "disabled", null);
        }

        writer.endElement("input");
    }

    protected void encodeSubTable(FacesContext context, DataTable table, SubTable subTable, int first, int last) throws IOException {
        LOGGER.info("SubTable has been deprecated, use row grouping instead");
        for (int i = first; i < last; i++) {
            table.setRowIndex(i);
            if (!table.isRowAvailable()) {
                break;
            }

            subTable.encodeAll(context);
        }
    }

    protected boolean isInSameGroup(FacesContext context, DataTable table, int currentRowIndex, int step, ValueExpression groupByVE,
                                    ELContext elContext) {

        table.setRowIndex(currentRowIndex);
        Object currentGroupByData = groupByVE.getValue(elContext);

        int nextRowIndex = currentRowIndex + step;

        Object nextGroupByData;

        // in case of a lazy DataTable, the LazyDataModel currently only loads rows inside the current page; we need a small hack here
        // 1) get the rowData manually for the next row
        // 2) put it into request-scope
        // 3) invoke the groupBy ValueExpression
        if (table.isLazy()) {
            Object nextRowData = table.getLazyDataModel().getRowData(nextRowIndex, table.getActiveSortMeta(), table.getActiveFilterMeta());
            if (nextRowData == null) {
                return false;
            }

            nextGroupByData = ComponentUtils.executeInRequestScope(context, table.getVar(), nextRowData, () -> {
                return groupByVE.getValue(elContext);
            });
        }
        else {
            table.setRowIndex(nextRowIndex);
            if (!table.isRowAvailable()) {
                return false;
            }

            nextGroupByData = groupByVE.getValue(elContext);
        }

        return Objects.equals(nextGroupByData, currentGroupByData);
    }

    protected void encodeSortableHeaderOnReflow(FacesContext context, DataTable table) throws IOException {
        ResponseWriter writer = context.getResponseWriter();
        Map headers = getSortableColumnHeaders(context, table);

        if (!headers.isEmpty()) {
            String reflowId = table.getContainerClientId(context) + "_reflowDD";

            writer.startElement("label", null);
            writer.writeAttribute("id", reflowId + "_label", null);
            writer.writeAttribute("for", reflowId, null);
            writer.writeAttribute("class", "ui-reflow-label", null);
            writer.writeText(MessageFactory.getMessage(DataTable.SORT_LABEL), null);
            writer.endElement("label");

            writer.startElement("select", null);
            writer.writeAttribute("id", reflowId, null);
            writer.writeAttribute("name", reflowId, null);
            writer.writeAttribute("class", "ui-reflow-dropdown ui-state-default", null);
            writer.writeAttribute("autocomplete", "off", null);

            for (Map.Entry header : headers.entrySet()) {
                for (int sortOrder = 0; sortOrder < 2; sortOrder++) {
                    String sortOrderLabel = (sortOrder == 0)
                                      ? MessageFactory.getMessage(DataTable.SORT_ASC)
                                      : MessageFactory.getMessage(DataTable.SORT_DESC);

                    writer.startElement("option", null);
                    writer.writeAttribute("value", header.getKey().getColumnKey() + "_" + sortOrder, null);
                    writer.writeAttribute("data-columnkey", header.getKey().getColumnKey(), null);
                    writer.writeAttribute("data-sortorder", sortOrder, null);
                    writer.writeText(header.getValue() + " " + sortOrderLabel, null);
                    writer.endElement("option");
                }
            }

            writer.endElement("select");
        }
    }

    protected Map getSortableColumnHeaders(FacesContext context, DataTable table) {
        AtomicReference headerLabel = new AtomicReference<>(null);

        Map sortByAsMap = table.getSortByAsMap();
        Map headers = new LinkedHashMap<>(sortByAsMap.size());
        for (SortMeta sortMeta : sortByAsMap.values()) {
            if (sortMeta.isHeaderRow()) {
                continue;
            }

            headerLabel.set(null);
            table.invokeOnColumn(sortMeta.getColumnKey(), (column) -> {
                headerLabel.set(getHeaderLabel(context, column));
            });
            headers.put(sortMeta, headerLabel.get());
        }

        return headers;
    }

    protected boolean hasColumnDefaultRendering(DataTable table, UIColumn column) {
        return column.getChildren().isEmpty()
                && (table.getSortByAsMap().containsKey(column.getColumnKey())
                || table.getFilterByAsMap().containsKey(column.getColumnKey())
                || LangUtils.isNotBlank(column.getField()));

    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy