/*
* � Copyright IBM Corp. 2010, 2011
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.ibm.xsp.extlib.component.data;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.faces.FacesException;
import javax.faces.component.UIComponent;
import javax.faces.component.UIComponentBase;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.faces.event.PhaseId;
import javax.faces.render.Renderer;
import com.ibm.xsp.context.FacesContextEx;
import com.ibm.xsp.designer.context.XSPContext;
import com.ibm.xsp.extlib.util.ExtLibUtil;
import com.ibm.xsp.page.FacesComponentBuilder;
import com.ibm.xsp.util.DataPublisher.ShadowedObject;
import com.ibm.xsp.util.StateHolderUtil;
import com.ibm.xsp.util.TypedUtil;
/**
* Custom data iterator that renders the content of a collection (view).
* <p>
* This iterator provides some predefined parts used to render the final markup.
* </p>
*/
public class AbstractDataView extends UIDataSourceIterator {
public static final String COMPONENT_TYPE = "com.ibm.xsp.extlib.data.AbstractDataView"; //$NON-NLS-1$
public static final String AJAX_ID_SUFFIX = "_ajax"; // $NON-NLS-1$
public static final String FACET_HEADER = "header"; // $NON-NLS-1$
public static final String FACET_FOOTER = "footer"; // $NON-NLS-1$
public static final String FACET_PAGERTOP = "pagerTop"; // $NON-NLS-1$
public static final String FACET_PAGERTOPLEFT = "pagerTopLeft"; // $NON-NLS-1$
public static final String FACET_PAGERTOPRIGHT = "pagerTopRight"; // $NON-NLS-1$
public static final String FACET_PAGERBOTTOM = "pagerBottom"; // $NON-NLS-1$
public static final String FACET_PAGERBOTTOMLEFT = "pagerBottomLeft"; // $NON-NLS-1$
public static final String FACET_PAGERBOTTOMRIGHT = "pagerBottomRight"; // $NON-NLS-1$
public static final String FACET_SUMMARY = "summary"; // $NON-NLS-1$
public static final String FACET_DETAIL = "detail"; // $NON-NLS-1$
public static final String FACET_NOROWS = "noRows"; // $NON-NLS-1$
/**
* The default id for the inner auto-generated RowComponent control, is
* added as a container for the row contents in the data-processing phases
* of the lifecycle.
*/
public static final String ROW_ID = "_row"; // $NON-NLS-1$
private static final boolean ENABLE_PARTIAL_REFRESH_ROW = true;
// Flobal options
private Boolean showItemsFlat;
// High level columns
private SummaryColumn summaryColumn;
// Detail
private Boolean collapsibleDetail;
private Boolean detailsOnClient;
private Boolean disableHideRow;
// Page to open when the link is clicked
private String pageName;
private Boolean openDocAsReadonly;
// Styling
private String style;
private String styleClass;
private String rowStyle;
private String rowStyleClass;
// disableGetFacets used to prevent an infinite loop related to MKEE89RPXF
private transient boolean disableGetFacets;
private transient PhaseId processFacetsForPhase; // part of the workaround
// for SPR#MKEE8BFEDR
private static final String[] CONTAINER_FACET_NAMES = { FACET_HEADER,
FACET_PAGERTOP, FACET_PAGERTOPLEFT, FACET_PAGERTOPRIGHT,
FACET_PAGERBOTTOM, FACET_PAGERBOTTOMLEFT, FACET_PAGERBOTTOMRIGHT,
FACET_FOOTER, FACET_NOROWS, };
/**
* Note, the facet names based on {@link #FACET_EXTRA_N} are also row-based
* facets.
*/
private static final String[] ROW_FACET_NAMES = { FACET_SUMMARY,
FACET_DETAIL, };
// Internal component that renders a row
// This component is used to render a single row, for a partial refresh
public static class RowComponent extends UIComponentBase {
@Override
public String getFamily() {
return AbstractDataView.COMPONENT_FAMILY;
}
@Override
public Renderer getRenderer(FacesContext context) {
return getParent().getRenderer(context);
}
}
public AbstractDataView() {
}
public boolean isShowItemsFlat() {
if (showItemsFlat != null) {
return showItemsFlat;
}
ValueBinding vb = getValueBinding("showItemsFlat"); // $NON-NLS-1$
if (vb != null) {
Boolean b = (Boolean) vb.getValue(getFacesContext());
if (b != null) {
return b;
}
}
return false;
}
public void setShowItemsFlat(boolean showItemsFlat) {
this.showItemsFlat = showItemsFlat;
}
public boolean isRowRefresh(FacesContext context) {
if (ENABLE_PARTIAL_REFRESH_ROW) {
// This mode is only supported if a partial tree is rendered
if (((FacesContextEx) context).isAjaxWholeTreeRendered()) {
return false;
}
// There is an issue with IE refreshing a single row
if (/* ExtLibCompUtil.isXPages852() && */XSPContext
.getXSPContext(context).getUserAgent().isIE(0, 8)) {
return false;
}
return true;
}
return false;
}
public SummaryColumn getSummaryColumn() {
return this.summaryColumn;
}
public void setSummaryColumn(SummaryColumn summaryColumn) {
this.summaryColumn = summaryColumn;
}
public boolean isCollapsibleDetail() {
if (collapsibleDetail != null) {
return collapsibleDetail;
}
ValueBinding vb = getValueBinding("collapsibleDetail"); // $NON-NLS-1$
if (vb != null) {
Boolean b = (Boolean) vb.getValue(getFacesContext());
if (b != null) {
return b;
}
}
return false;
}
public void setCollapsibleDetail(boolean collapsibleDetail) {
this.collapsibleDetail = collapsibleDetail;
}
public boolean isDetailsOnClient() {
if (detailsOnClient != null) {
return detailsOnClient;
}
ValueBinding vb = getValueBinding("detailsOnClient"); // $NON-NLS-1$
if (vb != null) {
Boolean b = (Boolean) vb.getValue(getFacesContext());
if (b != null) {
return b;
}
}
return false;
}
public void setDetailsOnClient(boolean detailsOnClient) {
this.detailsOnClient = detailsOnClient;
}
public boolean isDisableHideRow() {
if (disableHideRow != null) {
return disableHideRow;
}
ValueBinding vb = getValueBinding("disableHideRow"); // $NON-NLS-1$
if (vb != null) {
Boolean b = (Boolean) vb.getValue(getFacesContext());
if (b != null) {
return b;
}
}
return false;
}
public void setDisableHideRow(boolean disableHideRow) {
this.disableHideRow = disableHideRow;
}
/**
* Returns the name of the page that should be opened when a link is clicked
* on this view.
*
* @return the pageName
*/
public String getPageName() {
if (pageName != null) {
return pageName;
}
ValueBinding binding = getValueBinding("pageName"); // $NON-NLS-1$
if (binding != null) {
return (String) binding.getValue(getFacesContext());
}
return null;
}
/**
* Sets the name of the page to be opened when the link on a row is clicked.
*
* @param name
* the pageName to set
*/
public void setPageName(String name) {
pageName = name;
}
public boolean isOpenDocAsReadonly() {
if (openDocAsReadonly != null) {
return openDocAsReadonly;
}
ValueBinding vb = getValueBinding("openDocAsReadonly"); // $NON-NLS-1$
if (vb != null) {
Boolean b = (Boolean) vb.getValue(getFacesContext());
if (b != null) {
return b;
}
}
return false;
}
public void setOpenDocAsReadonly(boolean openDocAsReadonly) {
this.openDocAsReadonly = openDocAsReadonly;
}
public String getStyle() {
if (null != this.style) {
return this.style;
}
ValueBinding vb = getValueBinding("style"); //$NON-NLS-1$
if (vb != null) {
return (String) vb.getValue(getFacesContext());
}
else {
return null;
}
}
public void setStyle(String style) {
this.style = style;
}
public String getStyleClass() {
if (null != this.styleClass) {
return this.styleClass;
}
ValueBinding vb = getValueBinding("styleClass"); //$NON-NLS-1$
if (vb != null) {
return (String) vb.getValue(getFacesContext());
}
else {
return null;
}
}
public void setStyleClass(String styleClass) {
this.styleClass = styleClass;
}
public String getRowStyle() {
if (null != this.rowStyle) {
return this.rowStyle;
}
ValueBinding vb = getValueBinding("rowStyle"); //$NON-NLS-1$
if (vb != null) {
return (String) vb.getValue(getFacesContext());
}
else {
return null;
}
}
public void setRowStyle(String rowStyle) {
this.rowStyle = rowStyle;
}
public String getRowStyleClass() {
if (null != this.rowStyleClass) {
return this.rowStyleClass;
}
ValueBinding vb = getValueBinding("rowStyleClass"); //$NON-NLS-1$
if (vb != null) {
return (String) vb.getValue(getFacesContext());
}
else {
return null;
}
}
public void setRowStyleClass(String rowStyleClass) {
this.rowStyleClass = rowStyleClass;
}
@Override
public void restoreState(FacesContext context, Object state) {
Object[] values = (Object[]) state;
super.restoreState(context, values[0]);
showItemsFlat = (Boolean) values[1];
summaryColumn = (SummaryColumn) StateHolderUtil.restoreObjectState(
context, this, values[2]);
collapsibleDetail = (Boolean) values[3];
detailsOnClient = (Boolean) values[4];
disableHideRow = (Boolean) values[5];
pageName = (String) values[6];
openDocAsReadonly = (Boolean) values[7];
style = (String) values[8];
styleClass = (String) values[9];
rowStyle = (String) values[10];
rowStyleClass = (String) values[11];
}
@Override
public Object saveState(FacesContext context) {
Object[] values = new Object[12];
values[0] = super.saveState(context);
values[1] = showItemsFlat;
values[2] = StateHolderUtil.saveObjectState(context, summaryColumn);
values[3] = collapsibleDetail;
values[4] = detailsOnClient;
values[5] = disableHideRow;
values[6] = pageName;
values[7] = openDocAsReadonly;
values[8] = style;
values[9] = styleClass;
values[10] = rowStyle;
values[11] = rowStyleClass;
return values;
}
@Override
public void buildContents(FacesContext context,
FacesComponentBuilder builder) throws FacesException {
// TODO update the UIDataView to add header/footer facets and allow
// prevent outerDiv/table
// like the UIDataIterator parent.
// the container facets are not repeated for each row
for (String facetName : getContainerFacetNames()) {
if (builder.isFacetAvailable(context, this, facetName)) {
builder.buildFacet(context, this, facetName);
}
}
RowComponent row = new RowComponent();
// TODO this ID is not sufficiently unique - should prefix it with the
// control id or allow it to auto-generate
// an ID or something. As it is, if you have 2 sibling xe:dataView or
// xe:forumViews on a page,
// they will both have the same clientID for the inner row container.
row.setId(ROW_ID);
TypedUtil.getChildren(this).add(row);
// the view children are repeated for each row
builder.buildChildren(context, row);
// the row facets are repeated for each row (they are decoded /
// re-rendered for each row)
for (String facetName : getRowFacetNames()) {
if (builder.isFacetAvailable(context, row, facetName)) {
builder.buildFacet(context, row, facetName);
}
}
}
/**
* The names of the facets that should be created in the child row control,
* and should be repeated for each row. The {@link #FACET_EXTRA_N} facet
* names are also repeated for each row, though they are not included in
* this array.
*
* @return
*/
protected String[] getRowFacetNames() {
return ROW_FACET_NAMES;
}
/**
* The names of the facets that should be created in the data view container
* control, and not repeated for each row.
*
* @return
*/
protected String[] getContainerFacetNames() {
return CONTAINER_FACET_NAMES;
}
// Ajax support...
@Override
public String getAjaxContainerClientId(FacesContext context) {
String id = getClientId(context);
return id + AJAX_ID_SUFFIX;
}
/*
* (non-Javadoc)
*
* @see
* com.ibm.xsp.component.UIDataIterator#restoreValueHolderState(javax.faces
* .context.FacesContext, javax.faces.component.UIComponent)
*/
@SuppressWarnings("unchecked")//$NON-NLS-1$
@Override
protected void restoreValueHolderState(FacesContext context,
UIComponent component) {
super.restoreValueHolderState(context, component);
// start workaround for MKEE89RPXF
if (ExtLibUtil.isXPages852()) {
if (component.getFacetCount() > 0) {
Map<String, UIComponent> facets;
if (disableGetFacets && component == this) {
facets = super.getFacets();
}
else {
facets = TypedUtil.getFacets(component);
}
for (UIComponent c : facets.values()) {
if (c != null) {
restoreValueHolderState(context, c);
}
}
}
}
// end workaround for MKEE89RPXF
}
/*
* (non-Javadoc)
*
* @see com.ibm.xsp.component.UIDataIterator#setRowIndex(int)
*/
@Override
public void setRowIndex(int index) {
// disableGetFacets used to prevent an infinite loop when there
// are both facets on the data view control, and some facet on the
// rowComponent or any descendant control, related to MKEE89RPXF.
if (ExtLibUtil.isXPages852()) {
disableGetFacets = true;
}
try {
super.setRowIndex(index);
}
finally {
disableGetFacets = false;
}
}
/*
* (non-Javadoc)
*
* @see javax.faces.component.UIComponentBase#getFacets()
*/
@SuppressWarnings("rawtypes")//$NON-NLS-1$
@Override
public Map getFacets() {
if (disableGetFacets) {
return Collections.emptyMap();
}
return super.getFacets();
}
/*
* (non-Javadoc)
*
* @see
* com.ibm.xsp.component.UIDataIterator#saveValueHolderState(javax.faces
* .context.FacesContext, javax.faces.component.UIComponent)
*/
@SuppressWarnings("unchecked")//$NON-NLS-1$
@Override
protected void saveValueHolderState(FacesContext context,
UIComponent component) {
super.saveValueHolderState(context, component);
// start workaround for MKEE89RPXF
if (ExtLibUtil.isXPages852()) {
if (component.getFacetCount() > 0) {
Map<String, UIComponent> facets;
if (disableGetFacets && component == this) {
facets = super.getFacets();
}
else {
facets = TypedUtil.getFacets(component);
}
for (UIComponent c : facets.values()) {
if (c != null) {
saveValueHolderState(context, c);
}
}
}
}
// end workaround for MKEE89RPXF
}
/*
* (non-Javadoc)
*
* @see
* com.ibm.xsp.extlib.component.data.UIDataSourceIterator#processDecodes
* (javax.faces.context.FacesContext)
*/
@Override
public void processDecodes(FacesContext context) {
if (ExtLibUtil.isXPages852()) {
processFacetsForPhase = PhaseId.APPLY_REQUEST_VALUES;
}
try {
super.processDecodes(context);
}
finally {
processFacetsForPhase = null;
}
}
/*
* (non-Javadoc)
*
* @see
* com.ibm.xsp.extlib.component.data.UIDataSourceIterator#processUpdates
* (javax.faces.context.FacesContext)
*/
@Override
public void processUpdates(FacesContext context) {
if (ExtLibUtil.isXPages852()) {
processFacetsForPhase = PhaseId.UPDATE_MODEL_VALUES;
}
try {
super.processUpdates(context);
}
finally {
processFacetsForPhase = null;
}
}
/*
* (non-Javadoc)
*
* @see
* com.ibm.xsp.extlib.component.data.UIDataSourceIterator#processValidators
* (javax.faces.context.FacesContext)
*/
@Override
public void processValidators(FacesContext context) {
if (ExtLibUtil.isXPages852()) {
processFacetsForPhase = PhaseId.PROCESS_VALIDATIONS;
}
try {
super.processValidators(context);
}
finally {
processFacetsForPhase = null;
}
}
/*
* (non-Javadoc)
*
* @see
* com.ibm.xsp.component.UIDataIterator#revokeControlData(java.util.List,
* javax.faces.context.FacesContext)
*/
@Override
protected void revokeControlData(List<ShadowedObject> shadowedObjects,
FacesContext context) {
if (null != processFacetsForPhase && ExtLibUtil.isXPages852()) {
try {
// workaround for SPR#MKEE8BFEDR: Repeat, input values in header
// are not processed when submitted.
if (getFacetCount() > 0) {
int phaseOrdinal = processFacetsForPhase.getOrdinal();
for (UIComponent facet : TypedUtil.getFacets(this).values()) {
if (!facet.isRendered()) {
continue;
}
if (phaseOrdinal == PhaseId.APPLY_REQUEST_VALUES
.getOrdinal()) {
facet.processDecodes(context);
continue;
}
if (phaseOrdinal == PhaseId.UPDATE_MODEL_VALUES
.getOrdinal()) {
facet.processUpdates(context);
continue;
}
if (phaseOrdinal == PhaseId.PROCESS_VALIDATIONS
.getOrdinal()) {
facet.processValidators(context);
continue;
}
}
}
}
finally {
processFacetsForPhase = null;
}
}
super.revokeControlData(shadowedObjects, context);
}
}