Package org.openfaces.renderkit.table

Source Code of org.openfaces.renderkit.table.AbstractTableRenderer

/*
* OpenFaces - JSF Component Library 2.0
* Copyright (C) 2007-2012, TeamDev Ltd.
* licensing@openfaces.org
* Unless agreed in writing the contents of this file are subject to
* the GNU Lesser General Public License Version 2.1 (the "LGPL" License).
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* Please visit http://openfaces.org/licensing/ for more details.
*/
package org.openfaces.renderkit.table;

import org.openfaces.component.CaptionButton;
import org.openfaces.component.TableStyles;
import org.openfaces.component.command.MenuItem;
import org.openfaces.component.command.PopupMenu;
import org.openfaces.component.table.*;
import org.openfaces.component.table.impl.NodeInfo;
import org.openfaces.org.json.JSONArray;
import org.openfaces.org.json.JSONException;
import org.openfaces.org.json.JSONObject;
import org.openfaces.renderkit.AjaxPortionRenderer;
import org.openfaces.renderkit.CaptionButtonRenderer;
import org.openfaces.renderkit.RendererBase;
import org.openfaces.renderkit.TableUtil;
import org.openfaces.util.AjaxUtil;
import org.openfaces.util.Environment;
import org.openfaces.util.Log;
import org.openfaces.util.Rendering;
import org.openfaces.util.Resources;
import org.openfaces.util.ScriptBuilder;
import org.openfaces.util.StyleGroup;
import org.openfaces.util.Styles;

import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* @author Dmitry Pikhulya
*/
public abstract class AbstractTableRenderer extends RendererBase implements AjaxPortionRenderer {

    private static final String DEFAULT_SORTED_COLUMN_CLASS = null;//"o_table_sorted_column";
    private static final String DEFAULT_SORTED_COLUMN_HEADER_CLASS = "o_table_sorted_column_header";
    private static final String DEFAULT_SORTED_COLUMN_BODY_CLASS = "o_table_sorted_column_body";
    private static final String DEFAULT_SORTED_COLUMN_FOOTER_CLASS = "o_table_sorted_column_footer";
    public static final String DEFAULT_SORTABLE_HEADER_CLASS = "o_table_sortable_header";
    private static final String DEFAULT_SORTABLE_HEADER_ROLLOVER_CLASS = null;//"o_table_sortable_header_rollover";
    private static final String DEFAULT_FOCUSED_STYLE = "border: 1px dotted black;";

    private static final String FACET_COLUMN_MENU = "columnMenu";
    private static final String FACET_COLUMN_MENU_BUTTON = "columnMenuButton";
    public static final String DEFAULT_TOGGLE_CLASS_NAME = "o_treetable_expansionToggle";
    protected static final String SUB_ROWS_PORTION = "subRows:";

    public static String getTableJsURL(FacesContext context) {
        return Resources.internalURL(context, "table/table.js");
    }

    public static String treeTableJsURL(FacesContext context) {
        return Resources.internalURL(context, "table/treeTable.js");
    }

    @Override
    public void encodeBegin(FacesContext context, UIComponent component) throws IOException {

        if (!component.isRendered())
            return;

        final AbstractTable table = (AbstractTable) component;

        if (table.getUseAjax())
            AjaxUtil.prepareComponentForAjax(context, component);

        TableStructure tableStructure = createTableStructure(table);
        table.getAttributes().put(TableStructure.TABLE_STRUCTURE_ATTR, tableStructure);
        try {
            // this hack is needed for working around strange IE issue
            // JSFC-2081 ExpressionFilter drop-downs in TreeTable have improper style on demo (regression) - IE only
            encodeJsLinks(context);

            tableStructure.render(context, new HeaderCell.AdditionalContentWriter() {
                public void writeAdditionalContent(FacesContext context) throws IOException {
                    encodeScriptsAndStyles(context, table);
                }
            });
        } finally {
            table.getAttributes().remove(TableStructure.TABLE_STRUCTURE_ATTR);
        }
    }

    protected TableStructure createTableStructure(final AbstractTable table) {
        return new TableStructure(table, table) {
            protected String getAdditionalRowClass(FacesContext context, AbstractTable table, Object rowData, int rowIndex) {
                return AbstractTableRenderer.this.getAdditionalRowClass(context, table, rowData, rowIndex);
            }

            protected String[][] getBodyRowAttributes(FacesContext context, AbstractTable table) throws IOException {
                return AbstractTableRenderer.this.getBodyRowAttributes(context, table);
            }

            protected String getTextClass(AbstractTable table) {
                return AbstractTableRenderer.this.getTextClass(table);
            }

            protected String getTextStyle(AbstractTable table) {
                return AbstractTableRenderer.this.getTextStyle(table);
            }
        };
    }


    private void encodeJsLinks(FacesContext context) throws IOException {
        String[] libs = getNecessaryJsLibs(context);
        for (String lib : libs) {
            Resources.renderJSLinkIfNeeded(context, lib);
        }
    }

    protected String getTextClass(AbstractTable table) {
        return null;
    }

    protected String getTextStyle(AbstractTable table) {
        return null;
    }

    protected String getAdditionalRowClass(FacesContext context, AbstractTable table, Object rowData, int rowIndex) {
        return null;
    }


    protected String[][] getBodyRowAttributes(FacesContext context, AbstractTable table) throws IOException {
        return null;
    }

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

    @Override
    public void encodeChildren(FacesContext context, UIComponent uiComponent) throws IOException {
    }

    protected void encodeScriptsAndStyles(FacesContext context, AbstractTable table) throws IOException {
        ResponseWriter writer = context.getResponseWriter();
        writer.startElement("span", table);
        writer.writeAttribute("id", table.getClientId(context) + "::auxiliaryTags", null);
        writer.writeAttribute("style", "display: none", null);
        encodeAdditionalFeaturesSupport(context, table);
        Styles.renderStyleClasses(context, table);
        writer.endElement("span");
    }

    protected void encodeAdditionalFeaturesSupport(FacesContext context, AbstractTable table) throws IOException {
        ScriptBuilder buf = new ScriptBuilder();

        encodeAdditionalFeaturesSupport_buf(context, table, buf);

        AbstractTableSelection selection = table.getSelection();
        if (selection != null)
            selection.registerSelectionStyle(context);

        Styles.renderStyleClasses(context, table); // encoding styles before scripts is important for tableUtil.js to be able to compute row and column styles correctly

        String[] libs = getNecessaryJsLibs(context);
        Rendering.renderInitScript(context, buf, libs);

        if (selection != null)
            selection.encodeAll(context);

        ColumnResizing columnResizing = table.getColumnResizing();
        if (columnResizing != null)
            columnResizing.encodeAll(context);

        ColumnReordering columnReordering = table.getColumnReordering();
        if (columnReordering != null)
            columnReordering.encodeAll(context);

        Iterable<Summary> summaries = table.getSummaryComponents();
        for (Summary summary : summaries) {
            summary.encodeAfterCalculation(context);
        }
    }

    protected void encodeAdditionalFeaturesSupport_buf(FacesContext context, AbstractTable table, ScriptBuilder buf) throws IOException {
        encodeInitialization(context, table, buf);
        encodeKeyboardSupport(context, table, buf);
        encodeSortingSupport(context, table, buf);
        encodeColumnMenuSupport(context, table, buf);

        if (!table.isDataSourceEmpty())
            preregisterNoFilterDataRowStyleForOpera(context, table);

        encodeCheckboxColumnSupport(context, table, buf);
    }

    protected void encodeAdditionalFeaturesOnBodyReload(FacesContext context, AbstractTable table, ScriptBuilder sb) throws IOException {
        encodeSortingSupportOnBodyReload(context, table, sb);

        AbstractTableSelection selection = table.getSelection();
        if (selection != null)
            selection.encodeOnBodyReload(context, sb);

        ColumnResizing columnResizing = table.getColumnResizing();
        if (columnResizing != null) {
            columnResizing.encodeOnBodyReload(context, sb);
        }

        ColumnReordering columnReordering = table.getColumnReordering();
        if (columnReordering != null) {
            columnReordering.encodeOnBodyReload(context, sb);
        }

    }

    protected void encodeSortingSupportOnBodyReload(FacesContext context, AbstractTable table, ScriptBuilder sb) {

    }

    private void encodeColumnMenuSupport(FacesContext context, AbstractTable table, ScriptBuilder buf) throws IOException {
        UIComponent component = table.getFacet(FACET_COLUMN_MENU);
        if (component == null) return;
        if (!(component instanceof PopupMenu))
            throw new FacesException(
                    "The component inside of \"" + FACET_COLUMN_MENU + "\" facet must be a PopupMenu or descendant component, " +
                            "though the following component was found: " + component.getClass().getName() +
                            ". table id: \"" + table.getClientId(context) + "\"");
        UIComponent buttonComponent = table.getFacet(FACET_COLUMN_MENU_BUTTON);
        if (buttonComponent != null && !(buttonComponent instanceof CaptionButton))
            throw new FacesException(
                    "The component inside of \"" + FACET_COLUMN_MENU_BUTTON + "\" facet must be a CaptionButton or descendant component, " +
                            "though the following component was found: " + buttonComponent.getClass().getName() +
                            ". table id: \"" + table.getClientId(context) + "\"");
        CaptionButton button = (CaptionButton) buttonComponent;
        boolean temporaryButton = false;
        if (button == null) {
            button = createDefaultColumnMenuButton(context);
            temporaryButton = true;
            table.getFacets().put(FACET_COLUMN_MENU_BUTTON, button);
        }
        if (button.getImageUrl() == null)
            button.setImageUrl(getDefaultColumnMenuBtnImage(context));

        button.getAttributes().put(CaptionButtonRenderer.ATTR_DEFAULT_STYLE_CLASS, "o_columnMenuInvoker");

        // Rendering captionButton.js in this place of the generated HTML markup is required for fixing OF-69.
        // The reason is that IE cannot find the <script> tag for captionButton.js via the getElementsByTagName("script")
        // call in O$._markPreloadedLibraries (this <script> entry is just missing in this case, which seems to be some
        // IE bug) -- the result prior to fix was that ajaxUtil waited for captionButton.js to load indefinitely and failed
        // to invoke initialization scripts. Rendering captionButton.js in a less "deep" place of DOM solves the problem.
        Resources.renderJSLinkIfNeeded(context, Resources.utilJsURL(context));
        Resources.renderJSLinkIfNeeded(context, Resources.internalURL(context, "captionButton.js"));

        ResponseWriter writer = context.getResponseWriter();
        // mock table/tr enclosing tags must be rendered for IE8 to process the button's td tag properly
        writer.startElement("table", table);
        writer.startElement("tr", table);
        button.encodeAll(context);
        writer.endElement("tr");
        writer.endElement("table");

        PopupMenu columnMenu = (PopupMenu) component;
        columnMenu.setStandalone(true);
        columnMenu.encodeAll(context);

        // todo: move item identification responsibility to the items themselves
        MenuItem sortAscMenuItem = null, sortDescMenuItem = null, hideColumnMenuItem = null,
                groupByColumnMenuItem = null, removeFromGroupingMenuItem = null, cancelGroupingMenuItem = null,
                resetSortingMenuItem = null;
        for (UIComponent child : columnMenu.getChildren()) {
            if (child instanceof SortAscendingMenuItem)
                sortAscMenuItem = (MenuItem) child;
            else if (child instanceof SortDescendingMenuItem)
                sortDescMenuItem = (MenuItem) child;
            else if (child instanceof HideColumnMenuItem)
                hideColumnMenuItem = (MenuItem) child;
            else if (child instanceof GroupByColumnMenuItem)
                groupByColumnMenuItem = (MenuItem) child;
            else if (child instanceof RemoveFromGroupingMenuItem)
                removeFromGroupingMenuItem = (MenuItem) child;
            else if (child instanceof CancelGroupingMenuItem)
                cancelGroupingMenuItem = (MenuItem) child;
            else if (child instanceof ResetSortingMenuItem)
                resetSortingMenuItem = (MenuItem) child;
        }

        buf.initScript(context, columnMenu, "O$.ColumnMenu._init", table, button,
                sortAscMenuItem, sortDescMenuItem, hideColumnMenuItem, groupByColumnMenuItem,
                removeFromGroupingMenuItem, cancelGroupingMenuItem, resetSortingMenuItem);

        if (temporaryButton)
            table.getFacets().remove(FACET_COLUMN_MENU_BUTTON);
    }

    private CaptionButton createDefaultColumnMenuButton(FacesContext context) {
        CaptionButton captionButton = new CaptionButton();
        captionButton.setId("_columnMenuButton" + Rendering.SERVER_ID_SUFFIX_SEPARATOR);
        captionButton.setImageUrl(getDefaultColumnMenuBtnImage(context));
        return captionButton;
    }

    private String getDefaultColumnMenuBtnImage(FacesContext context) {
        return Resources.internalURL(context, null, "table/columnMenuDrop.gif", false);
    }

    private void preregisterNoFilterDataRowStyleForOpera(FacesContext context, AbstractTable table) {
        if (Environment.isOpera() || Environment.isUndefinedBrowser())
            TableStructure.getNoDataRowClassName(context, table);
    }

    private void encodeInitialization(
            FacesContext context,
            AbstractTable table,
            ScriptBuilder buf) throws IOException {
        TableStyles defaultStyles = TableStructure.getDefaultStyles(table);
        TableStructure tableStructure = TableStructure.getCurrentInstance(table);

        buf.initScript(context, table, "O$.Table._init",
                tableStructure.getInitParam(context, defaultStyles),
                table.getUseAjax(),
                Styles.getCSSClass(context, table, table.getRolloverStyle(),
                        StyleGroup.rolloverStyleGroup(), table.getRolloverClass()),
                getInitJsAPIFunctionName(),
                table.getDeferBodyLoading());
    }

    protected String getInitJsAPIFunctionName() {
        return "";
    }

    protected String[] getNecessaryJsLibs(FacesContext context) {
        return new String[]{
                Resources.utilJsURL(context),
                TableUtil.getTableUtilJsURL(context),
                getTableJsURL(context),
                Resources.jsonJsURL(context)
        };
    }

    private void encodeKeyboardSupport(FacesContext context, AbstractTable table, ScriptBuilder buf) throws IOException {
        boolean focusable = isKeyboardNavigationApplicable(table);
        if (!focusable)
            return;

        Boolean focusedAttr = (Boolean) table.getAttributes().get("focused");
        ResponseWriter writer = context.getResponseWriter();
        String focusFieldName = getFocusFieldName(context, table);
        String focused = String.valueOf(focusedAttr != null && focusedAttr);
        Rendering.renderHiddenField(writer, focusFieldName, focused);
        boolean tableIsPaginated = getUseKeyboardForPagination(table);
        boolean applyDefaultStyle = table.getApplyDefaultStyle();
        String focusedClass = Styles.getCSSClass_dontCascade(
                context, table, table.getFocusedStyle(), StyleGroup.selectedStyleGroup(), table.getFocusedClass(),
                applyDefaultStyle ? DEFAULT_FOCUSED_STYLE : null);

        boolean canPageBack = tableIsPaginated && canPageBack(table);
        boolean canPageForth = tableIsPaginated && canPageForth(table);
        boolean canSelectLastPage = tableIsPaginated && canSelectLastPage(table);
        buf.initScript(context, table, "O$.Table._initKeyboardNavigation",
                tableIsPaginated,
                focusedClass,
                canPageBack,
                canPageForth,
                canSelectLastPage,
                table.getTabindex());
    }

    protected boolean canSelectLastPage(AbstractTable table) {
        return false;
    }

    protected boolean canPageForth(AbstractTable table) {
        return false;
    }

    protected boolean canPageBack(AbstractTable table) {
        return false;
    }

    protected boolean getUseKeyboardForPagination(AbstractTable table) {
        return false;
    }

    private String getFocusFieldName(FacesContext context, AbstractTable table) {
        return table.getClientId(context) + "::focused";
    }

    private void encodeSortingSupport(FacesContext context, AbstractTable table, ScriptBuilder buf) throws IOException {
        boolean atLeastOneColumnSortable1 = false;
        final JSONArray sortableColumnsIds = new JSONArray();
        for (BaseColumn column : table.getAllColumns()) {
            boolean sortable;
            Boolean columnSortableAttr = (Boolean) column.getAttributes().get("sortable");
            if (columnSortableAttr != null)
                sortable = columnSortableAttr;
            else {
                ValueExpression sortingExpression = column.getColumnSortingExpression();
                sortable = (sortingExpression != null);
            }
            if (sortable) sortableColumnsIds.put(column.getId());
            atLeastOneColumnSortable1 |= sortable;
        }
        boolean atLeastOneColumnSortable = atLeastOneColumnSortable1;
        if (!atLeastOneColumnSortable)
            return;

        getSortedColumnClass(table);
        getSortedColumnHeaderClass(table);

        ResponseWriter writer = context.getResponseWriter();
        Rendering.renderHiddenField(writer, getSortingFieldName(context, table), null);

        buf.initScript(context, table, "O$.Table._initSorting",
                table.getSortingRules(),
                sortableColumnsIds,
                table.getSortColumnIndex(),
                Styles.getCSSClass(context, table, table.getSortableHeaderStyle(), StyleGroup.regularStyleGroup(), getSortableHeaderClass(table)),
                Styles.getCSSClass(context, table, table.getSortableHeaderRolloverStyle(), StyleGroup.regularStyleGroup(), getSortableHeaderRolloverClass(table)),
                Styles.getCSSClass(context, table, table.getSortedColumnStyle(), StyleGroup.regularStyleGroup(), getSortedColumnClass(table)),
                Styles.getCSSClass(context, table, table.getSortedColumnHeaderStyle(), StyleGroup.regularStyleGroup(), getSortedColumnHeaderClass(table)),
                Styles.getCSSClass(context, table, table.getSortedColumnBodyStyle(), StyleGroup.regularStyleGroup(), getSortedColumnBodyClass(table)),
                Styles.getCSSClass(context, table, table.getSortedColumnFooterStyle(), StyleGroup.regularStyleGroup(), getSortedColumnFooterClass(table)),
                HeaderCell.getSortedAscendingImageUrl(context, table),
                HeaderCell.getSortedDescendingImageUrl(context, table),
                table.getUnsortedStateAllowed());
    }

    private String getSortingFieldName(FacesContext context, UIComponent table) {
        return table.getClientId(context) + "::sorting";
    }

    private String getSortedColumnClass(AbstractTable table) {
        String sortedColumnClass = table.getSortedColumnClass();
        if (!table.getApplyDefaultStyle())
            return sortedColumnClass;
        return TableUtil.getClassWithDefaultStyleClass(table.getApplyDefaultStyle(), DEFAULT_SORTED_COLUMN_CLASS, sortedColumnClass);
    }

    private String getSortedColumnHeaderClass(AbstractTable table) {
        String sortedColumnHeaderClass = table.getSortedColumnHeaderClass();
        if (!table.getApplyDefaultStyle())
            return sortedColumnHeaderClass;
        return TableUtil.getClassWithDefaultStyleClass(table.getApplyDefaultStyle(), DEFAULT_SORTED_COLUMN_HEADER_CLASS, sortedColumnHeaderClass);
    }

    private String getSortedColumnBodyClass(AbstractTable table) {
        String sortedColumnBodyClass = table.getSortedColumnBodyClass();
        if (!table.getApplyDefaultStyle())
            return sortedColumnBodyClass;
        return TableUtil.getClassWithDefaultStyleClass(table.getApplyDefaultStyle(), DEFAULT_SORTED_COLUMN_BODY_CLASS, sortedColumnBodyClass);
    }

    private String getSortedColumnFooterClass(AbstractTable table) {
        String sortedColumnFooterClass = table.getSortedColumnFooterClass();
        if (!table.getApplyDefaultStyle())
            return sortedColumnFooterClass;
        return TableUtil.getClassWithDefaultStyleClass(table.getApplyDefaultStyle(), DEFAULT_SORTED_COLUMN_FOOTER_CLASS, sortedColumnFooterClass);
    }

    private String getSortableHeaderClass(AbstractTable table) {
        String sortableHeaderClass = table.getSortableHeaderClass();
        if (!table.getApplyDefaultStyle())
            return sortableHeaderClass;
        return TableUtil.getClassWithDefaultStyleClass(table.getApplyDefaultStyle(), DEFAULT_SORTABLE_HEADER_CLASS, sortableHeaderClass);
    }

    private String getSortableHeaderRolloverClass(AbstractTable table) {
        String sortableHeaderRolloverClass = table.getSortableHeaderRolloverClass();
        if (!table.getApplyDefaultStyle())
            return sortableHeaderRolloverClass;
        return TableUtil.getClassWithDefaultStyleClass(table.getApplyDefaultStyle(), DEFAULT_SORTABLE_HEADER_ROLLOVER_CLASS, sortableHeaderRolloverClass);
    }

    @Override
    public void decode(FacesContext context, UIComponent uiComponent) {
        super.decode(context, uiComponent);
        if (!uiComponent.isRendered())
            return;
        AbstractTable table = (AbstractTable) uiComponent;

        decodeKeyboardSupport(context, table);
        AbstractTableSelection selection = table.getSelection();
        if (selection != null)
            selection.processDecodes(context);

        ColumnResizing columnResizing = table.getColumnResizing();
        if (columnResizing != null)
            columnResizing.processDecodes(context);

        decodeSorting(context, table);
        decodeColumnMenu(context, table);

        decodeCheckboxColumnSupport(context, table);

        Scrolling scrolling = table.getScrolling();
        if (scrolling != null)
            scrolling.processDecodes(context);

        decodeColumnsOrder(context, table);

        decodeFoldingSupport(context, table);
    }

    protected void decodeColumnsOrder(FacesContext context, AbstractTable table) {
        Map<String, String> params = context.getExternalContext().getRequestParameterMap();
        String paramName = table.getClientId(context) + "::columnsOrder";
        String renderedColumns = params.get(paramName);
        if (renderedColumns == null)
            return;

        if (renderedColumns.length() == 0) {
            Log.log("columnsOrder list should contain at least one column");
            return;
        }
        List<String> columnIds = Arrays.asList(renderedColumns.split(","));
        table.getAttributes().put("submittedColumnsOrder", columnIds);
    }


    private void decodeKeyboardSupport(FacesContext context, AbstractTable table) {
        Map<String, String> requestParameterMap = context.getExternalContext().getRequestParameterMap();
        String focusedStr = requestParameterMap.get(getFocusFieldName(context, table));
        boolean focused = focusedStr != null && focusedStr.equals("true");
        table.getAttributes().put("focused", focused);
    }

    private void decodeSorting(FacesContext context, AbstractTable table) {
        Map<String, String> requestParameterMap = context.getExternalContext().getRequestParameterMap();
        String paramName = table.getClientId(context) + "::setSortingRules";
        String sortingRulesStr = requestParameterMap.get(paramName);
        if (Rendering.isNullOrEmpty(sortingRulesStr)) return;

        List<SortingRule> sortingRules = new ArrayList<SortingRule>();
        try {
            JSONArray sortingRulesJson = new JSONArray(sortingRulesStr);
            for (int i = 0, count = sortingRulesJson.length(); i < count; i++) {
                JSONObject jsonObject = sortingRulesJson.getJSONObject(i);
                sortingRules.add(new SortingRule(jsonObject));
            }
        } catch (JSONException e) {
            throw new RuntimeException(e);
        }
        table.acceptNewSortingRules(sortingRules);
    }

    private void decodeColumnMenu(FacesContext context, AbstractTable table) {
        // todo: make these work through the new standard setColumnsOrder client-side API and remove the special
        // decoding cases here
        decodeColumnVisibility(context, table);
        decodeColumnHiding(context, table);
    }

    private void decodeColumnVisibility(FacesContext context, AbstractTable table) {
        //todo: [s.kurilin] We don't need it now. Remove it carefully.
        Map<String, String> requestParameterMap = context.getExternalContext().getRequestParameterMap();
        String fieldName = table.getClientId(context) + "::columnVisibility";
        String fieldValue = requestParameterMap.get(fieldName);
        if (fieldValue != null) {
            int columnToToggle = Integer.parseInt(fieldValue);
            List<BaseColumn> allColumns = table.getAllColumns();
            List<BaseColumn> renderedColumns = table.getRenderedColumns();
            BaseColumn column = allColumns.get(columnToToggle);
            boolean columnWasVisible = renderedColumns.contains(column);
            boolean showColumn = !columnWasVisible;
            applyColumnVisibility(table, column, showColumn);
        }
    }

    private void decodeColumnHiding(FacesContext context, AbstractTable table) {
        Map<String, String> requestParameterMap = context.getExternalContext().getRequestParameterMap();
        String hideColumnStr = requestParameterMap.get(table.getClientId(context) + "::hideColumn");
        if (hideColumnStr != null) {
            List<BaseColumn> columns = table.getRenderedColumns();
            int colIndex = Integer.parseInt(hideColumnStr);
            BaseColumn column = columns.get(colIndex);
            applyColumnVisibility(table, column, false);
        }
    }

    private void applyColumnVisibility(AbstractTable table, BaseColumn column, boolean showColumn) {
        List<BaseColumn> allColumns = table.getAllColumns();
        List<BaseColumn> renderedColumns = table.getRenderedColumns();

        if (!showColumn && renderedColumns.size() == 1)
            showColumn = true; // don't allow to hide the last column
        List<String> newColumnsOrder = new ArrayList<String>();
        for (BaseColumn c : allColumns) {
            String cid = c.getId();
            if (c != column) {
                if (renderedColumns.contains(c))
                    newColumnsOrder.add(cid);
            } else {
                if (showColumn)
                    newColumnsOrder.add(cid);
            }
        }
        table.getAttributes().put("submittedColumnsOrder", newColumnsOrder);
    }


    protected boolean isKeyboardNavigationApplicable(AbstractTable table) {
        AbstractTableSelection selection = table.getSelection();
        boolean forSelection = selection != null && selection.isEnabled() && selection.isKeyboardSupport();
        boolean forPaging = getUseKeyboardForPagination(table);
        boolean result = forSelection || forPaging;
        return result;
    }

    private void encodeCheckboxColumnSupport(
            FacesContext context,
            AbstractTable table,
            ScriptBuilder buf
    ) throws IOException {
        for (BaseColumn column : table.getRenderedColumns()) {
            if (!(column instanceof CheckboxColumn))
                continue;

            ((CheckboxColumn) column).encodeInitScript(context, buf);
        }
    }

    protected void decodeCheckboxColumnSupport(FacesContext context, AbstractTable table) {
        Map<String, String> requestMap = context.getExternalContext().getRequestParameterMap();
        for (BaseColumn column : table.getRenderedColumns()) {
            if (!(column instanceof CheckboxColumn))
                continue;

            String colId = column.getClientId(context);
            String checkedRowIndexesStr = requestMap.get(colId);
            String[] indexes;
            if (checkedRowIndexesStr == null || checkedRowIndexesStr.length() == 0) {
                indexes = new String[0];
            } else {
                indexes = checkedRowIndexesStr.split(",");
            }
            List<Integer> rowIndexes = new ArrayList<Integer>(indexes.length);
            for (String indexStr : indexes) {
                Integer checkedRowIndex = new Integer(indexStr);
                rowIndexes.add(checkedRowIndex);
            }
            CheckboxColumn checkboxColumn = ((CheckboxColumn) column);
            checkboxColumn.decodeSelectionFromIndexes(rowIndexes);
        }
    }

    public JSONObject encodeAjaxPortion(FacesContext context, UIComponent component, String portionName, JSONObject jsonParam) throws IOException {
        AbstractTable table = (AbstractTable) component;
        if ("columnResizingState".equals(portionName)) {
            ColumnResizing columnResizing = table.getColumnResizing();
            // the execute phases are skipped for the "columnResizingState" Ajax request
            columnResizing.processDecodes(context);
            columnResizing.processValidators(context);
            columnResizing.processUpdates(context);
            return null;
        }
        if ("scrollingState".equals(portionName)) {
            Scrolling scrolling = table.getScrolling();
            // the execute phases are skipped for the "columnResizingState" Ajax request
            scrolling.processDecodes(context);
            scrolling.processValidators(context);
            scrolling.processUpdates(context);
            return null;
        }
        if ("rows".equals(portionName)) {
            beforeReloadingAllRows(context, table);
            JSONObject result = serveDynamicRowsRequest(context, table, 0, Integer.MAX_VALUE);

            ScriptBuilder sb = new ScriptBuilder();
            encodeAdditionalFeaturesOnBodyReload(context, table, sb);
            Rendering.renderInitScript(context, sb);

            return result;
        } else {
            throw new FacesException("Unknown portion name: " + portionName);
        }
    }

    protected void beforeReloadingAllRows(FacesContext context, AbstractTable table) {

    }

    protected JSONObject serveDynamicRowsRequest(
            FacesContext context,
            AbstractTable table,
            int rowIndex,
            int rowCount
    ) throws IOException {
        TableStructure tableStructure = createTableStructure(table);
        table.getAttributes().put(TableStructure.TABLE_STRUCTURE_ATTR, tableStructure);
        try {
            ResponseWriter responseWriter = context.getResponseWriter();
            Writer stringWriter = new StringWriter();
            ResponseWriter clonedResponseWriter = responseWriter.cloneWithWriter(stringWriter);
            context.setResponseWriter(clonedResponseWriter);
            try {
                if (rowCount > 0) {
                    List<BaseColumn> columns = table.getRenderedColumns();
                    List<BodyRow> rows = tableStructure.getBody().createRows(context, rowIndex, rowCount, columns);
                    if (tableStructure.getScrolling() == null) {
                        for (BodyRow row : rows) {
                            row.render(context, null);
                        }
                    } else {
                        if (rows.size() != 1)
                            throw new IllegalStateException("There should be one pseudo-row in the scrollable version");
                        BodyRow pseudoRow = rows.get(0);
                        List<BodyCell> cells = pseudoRow.getCells();
                        int ci = 0;
                        List<BodyRow> leftRows = tableStructure.getLeftFixedCols() > 0 ? getScrollingAreaRows(cells.get(ci++)) : null;
                        List<BodyRow> centerRows = getScrollingAreaRows(cells.get(ci++));
                        List<BodyRow> rightRows = tableStructure.getRightFixedCols() > 0 ? getScrollingAreaRows(cells.get(ci)) : null;
                        for (int i = 0, count = centerRows.size(); i < count; i++) {
                            if (leftRows != null)
                                leftRows.get(i).render(context, null);
                            centerRows.get(i).render(context, null);
                            if (rightRows != null)
                                rightRows.get(i).render(context, null);
                        }
                    }

                    AbstractTableSelection selection = table.getSelection();
                    if (selection != null)
                        selection.encodeOnAjaxNodeFolding(context);
                    for (BaseColumn column : columns) {
                        if (column instanceof CheckboxColumn)
                            ((CheckboxColumn) column).encodeOnAjaxNodeFolding(context);
                    }
                }
            } finally {
                context.setResponseWriter(responseWriter);
            }
            table.setRowIndex(-1);
            responseWriter.write(stringWriter.toString());

            JSONObject rowsInitInfo = new JSONObject();
            fillDynamicRowsInitInfo(context, table, rowIndex, rowCount, tableStructure, rowsInitInfo);
            return rowsInitInfo;
        } finally {
            table.getAttributes().remove(TableStructure.TABLE_STRUCTURE_ATTR);
        }

    }

    private List<BodyRow> getScrollingAreaRows(BodyCell scrollingAreaCell) {
        TableScrollingArea area = (TableScrollingArea) scrollingAreaCell.getContent();
        return (List<BodyRow>) area.getRows();
    }

    protected void fillDynamicRowsInitInfo(FacesContext context,
                                           AbstractTable table,
                                           int rowIndex,
                                           int addedRowCount,
                                           TableStructure tableStructure,
                                           JSONObject rowsInitInfo) {
        Map<Object, String> rowStylesMap = tableStructure.getRowStylesMap();
        Map<Object, String> cellStylesMap = tableStructure.getCellStylesMap();
        List<String> rowKeys = tableStructure.getBody().getClientRowKeys();
        Rendering.addJsonParam(rowsInitInfo, "rowStylesMap", TableUtil.getStylesMapAsJSONObject(rowStylesMap));
        Rendering.addJsonParam(rowsInitInfo, "cellStylesMap", TableUtil.getStylesMapAsJSONObject(cellStylesMap));
        Rendering.addJsonParam(rowsInitInfo, "rowKeys", rowKeys);
    }

    public static boolean isAjaxRowLoadingInProgress(FacesContext context) {
        return isAjaxPortionRequestInProgress(context, "rows");
    }

    protected static boolean isAjaxPortionRequestInProgress(FacesContext context, String expectedPortionName) {
        boolean ajaxRequestInProgress = AjaxUtil.isAjaxRequest(context);
        if (!ajaxRequestInProgress)
            return false;

        List<String> portions = AjaxUtil.getRequestedAjaxPortionNames(context);
        if (portions == null)
            return false;

        for (String portionName : portions) {
            if (portionName.startsWith(expectedPortionName))
                return true;
        }
        return false;
    }

    protected static boolean encodeFoldingSupport(FacesContext context,
                                                  ScriptBuilder buf,
                                                  AbstractTable table) throws IOException {
        JSONObject treeStructure = formatTreeStructureMap(context, table, -1, -1);
        if (treeStructure == null) {
            // this can be the case for a grouping-enabled table which doesn't have any grouping rule configured,
            // which means that it is displayed as a plain list and no special hierarchy handling is required
            return false;
        }

        ResponseWriter writer = context.getResponseWriter();
        Rendering.renderHiddenField(writer, getExpandedNodesFieldName(context, table), null);

        JSONArray treeColumnParamsArray = new JSONArray();
        List<BaseColumn> renderedColumns = table.getAdaptedRenderedColumns();
        for (BaseColumn column : renderedColumns) {
            if (!(column instanceof TreeColumn))
                continue;
            TreeColumn treeColumn = (TreeColumn) column;
            Object columnParams = treeColumn.encodeParamsAsJsObject(context);
            treeColumnParamsArray.put(columnParams != null ? columnParams : JSONObject.NULL);
        }

        buf.initScript(context, table, "O$.TreeTable._initFolding",
                treeStructure,
                treeColumnParamsArray,
                DEFAULT_TOGGLE_CLASS_NAME,
                Resources.internalURL(context, "table/treeStructureSolid.png"));
        return true;
    }

    private static String getExpandedNodesFieldName(FacesContext context, AbstractTable treeTable) {
        return treeTable.getClientId(context) + "::expandedNodes";
    }

    protected static JSONObject formatTreeStructureMap(
            FacesContext context,
            AbstractTable table,
            int fromRowIndex,
            int rowCount) {
        JSONObject result = new JSONObject();
        Map<Object, ? extends NodeInfo> structureMap = table.getTreeStructureMap(context);
        if (structureMap == null)
            return null;
        Set<? extends Map.Entry<Object, ? extends NodeInfo>> entries = structureMap.entrySet();
        for (Map.Entry<Object, ? extends NodeInfo> entry : entries) {
            Object rowIndex = entry.getKey();
            if (fromRowIndex != -1) {
                if (!(rowIndex instanceof Integer))
                    continue;
                int intRowIndex = (Integer) rowIndex;
                if (intRowIndex < fromRowIndex || intRowIndex >= fromRowIndex + rowCount)
                    continue;
                intRowIndex -= fromRowIndex;
                rowIndex = intRowIndex;
            }
            NodeInfo expansionData = entry.getValue();
            boolean nodeHasChildren = expansionData.getNodeHasChildren();
            Object childCount = nodeHasChildren
                    ? (expansionData.getChildrenPreloaded() ? String.valueOf(expansionData.getChildNodeCount()) : "?")
                    : 0;
            try {
                result.put(String.valueOf(rowIndex), childCount);
            } catch (JSONException e) {
                throw new RuntimeException(e);
            }
        }
        return result;
    }

    private void decodeFoldingSupport(FacesContext context, AbstractTable table) {
        Map<String, String> requestParameterMap = context.getExternalContext().getRequestParameterMap();
        String expandedNodes = requestParameterMap.get(getExpandedNodesFieldName(context, table));
        if (expandedNodes == null)
            return;
        String[] indexStrs = expandedNodes.split(",");
        Set<Integer> expandedRowIndexes = new HashSet<Integer>(indexStrs.length);
        for (String indexStr : indexStrs) {
            if (indexStr.length() == 0)
                continue;
            int rowIndex = Integer.parseInt(indexStr);
            expandedRowIndexes.add(rowIndex);
        }
        table.acceptNewExpandedRowIndexes(expandedRowIndexes);
    }

    public static boolean isAjaxFoldingInProgress(FacesContext context) {
        return isAjaxPortionRequestInProgress(context, SUB_ROWS_PORTION);
    }


}
TOP

Related Classes of org.openfaces.renderkit.table.AbstractTableRenderer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.