/* * � 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.io.IOException; import java.util.HashMap; import java.util.List; import javax.faces.FacesException; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.el.ValueBinding; import javax.faces.event.AbortProcessingException; import javax.faces.event.FacesEvent; import javax.faces.event.PhaseId; import javax.faces.model.DataModel; import com.ibm.commons.util.StringUtil; import com.ibm.xsp.binding.ComponentBindingObject; import com.ibm.xsp.component.FacesComponent; import com.ibm.xsp.component.FacesDataIterator; import com.ibm.xsp.component.UIDataIterator; import com.ibm.xsp.context.FacesContextEx; import com.ibm.xsp.extlib.component.data.FacesDataIteratorStateManager.Options; import com.ibm.xsp.extlib.component.data.FacesDataIteratorStateManager.State; import com.ibm.xsp.extlib.renderkit.html_extended.data.ToggleDetailEvent; import com.ibm.xsp.extlib.renderkit.html_extended.data.ToggleRowEvent; import com.ibm.xsp.extlib.renderkit.html_extended.data.ToggleSortColumnEvent; import com.ibm.xsp.extlib.stylekit.StyleKitExtLibDefault; import com.ibm.xsp.model.DataSource; import com.ibm.xsp.model.TabularDataModel; import com.ibm.xsp.page.FacesComponentBuilder; import com.ibm.xsp.page.parse.types.FacesInstance; import com.ibm.xsp.stylekit.ThemeControl; import com.ibm.xsp.util.DataPublisher; import com.ibm.xsp.util.DataPublisher.ShadowedObject; import com.ibm.xsp.util.FacesUtil; import com.ibm.xsp.util.HtmlUtil; /** * Base class for a {@link UIDataView} * <p> * This is a base class for data repeat control containing a data source. It renders * like the regular repeat control, but it adds a few behaviors like the management of * expanded/collapsed rows, partial processing, as well as an embedded data source.<br> * The implementation of a repeat control is complex, but this intention of this class * is to handle all the deep technical details and makes it easy to specialize. * </p> */ public class UIDataSourceIterator extends UIDataIterator implements FacesDataIteratorStateHandler, FacesComponent, FacesDataIteratorAjax, ThemeControl { public static final String COMPONENT_TYPE = "com.ibm.xsp.extlib.data.DataSourceIterator"; //$NON-NLS-1$ private DataSource data; private Boolean partialExecute; private Boolean partialRefresh; private String refreshId; private Boolean expandedDetail; // TODO verify the visibleDetails works correctly when this control is in a repeat iterating over different viewNames // there doesn't seem to be any code to handle the fact that this control // may be in a repeat that repeats over viewNames, with the data source view name // computed to use different values when in different rows of the repeat. private HashMap<String, Boolean> visibleDetails; // note, no need to keep a _shadowedObjects list and call // publish/revokeControlData during the process* methods, as that's already // handled in the superclass. transient private String _toggledVisibleDetail; // Just for rendering purposes when a position had been toggled /** * This is the key of a value stored in the attributes map and not a clientId suffix, * nor is it an actual clientId. It is used to maintain the currently focussed category * link within a View Panel during partial refresh expand/collapse interactions. */ public static final String TOGGLE_ACTION_CLIENT_ID = "__toggleActionClientId__"; //$NON-NLS-1$ public UIDataSourceIterator() { } public String getStyleKitFamily() { return StyleKitExtLibDefault.DATAITERATOR; } @Override public void _xspCleanTransientData() { super._xspCleanTransientData(); _toggledVisibleDetail = null; } /** * A component can implement FacesDefinitionClass to instruct the compiler * to use a different class. We stop it here the Data Iterator implementation. * A basic data iterator uses this to either crate the component that * creates the controls at load time, or iterate them dynamically at runtime. */ @Override public Class<? extends UIComponent> getJavaClass(FacesInstance instance) { return getClass(); } public boolean isPartialExecute() { if(partialExecute!=null) { return partialExecute; } ValueBinding vb = getValueBinding("partialExecute"); //$NON-NLS-1$ if(vb!=null) { Boolean b = (Boolean)vb.getValue(getFacesContext()); if(b!=null) { return b; } } return true; } public void setPartialExecute(boolean partialExecute) { this.partialExecute = partialExecute; } public boolean isPartialRefresh() { if(partialRefresh!=null) { return partialRefresh; } ValueBinding vb = getValueBinding("partialRefresh"); //$NON-NLS-1$ if(vb!=null) { Boolean b = (Boolean)vb.getValue(getFacesContext()); if(b!=null) { return b; } } return true; } public void setPartialRefresh(boolean partialRefresh) { this.partialRefresh = partialRefresh; } public String getRefreshId() { if(refreshId!=null) { return refreshId; } ValueBinding vb = getValueBinding("refreshId"); //$NON-NLS-1$ if(vb!=null) { return (String)vb.getValue(getFacesContext()); } return null; } public void setRefreshId(String partialRefreshId) { this.refreshId = partialRefreshId; } /** * returns the {@link com.ibm.xsp.model.DataSource} for this control. * @return */ public DataSource getData() { return data; } /** * Sets the {@link com.ibm.xsp.model.DataSource} for this control. * @param data */ public void setData(DataSource data) { this.data = data; if (data instanceof ComponentBindingObject) { ((ComponentBindingObject)data).setComponent(this); } } @Override public DataSource getDataSource() { DataSource ds = getData(); if (ds != null) { return ds; } return super.getDataSource(); } public boolean isExpandedDetail() { if(expandedDetail!=null) { return expandedDetail; } ValueBinding vb = getValueBinding("expandedDetail"); //$NON-NLS-1$ if(vb!=null) { Boolean b = (Boolean)vb.getValue(getFacesContext()); if(b!=null) { return b; } } return false; } public void setExpandedDetail(boolean detailExpanded) { this.expandedDetail = detailExpanded; visibleDetails = null; } public void showAll() { setExpandedDetail(true); } public boolean isShowAll() { return isExpandedDetail() && (visibleDetails==null || visibleDetails.isEmpty()); } public void hideAll() { setExpandedDetail(false); } public boolean isHideAll() { return !isExpandedDetail() && (visibleDetails==null || visibleDetails.isEmpty()); } @SuppressWarnings("unchecked") //$NON-NLS-1$ @Override public void restoreState(FacesContext context, Object state) { Object[] values = (Object[]) state; super.restoreState(context, values[0]); this.partialExecute = (Boolean)values[1]; this.partialRefresh = (Boolean)values[2]; this.refreshId = (String)values[3]; this.data = (DataSource) FacesUtil.objectFromSerializable(context, this, values[4]); this.visibleDetails = (HashMap<String, Boolean>)values[5]; this.expandedDetail = (Boolean)values[6]; } @Override public Object saveState(FacesContext context) { Object values[] = new Object[7]; values[0] = super.saveState(context); values[1] = partialExecute; values[2] = partialRefresh; values[3] = refreshId; values[4] = FacesUtil.objectToSerializable(context, data); values[5] = visibleDetails; values[6] = expandedDetail; return values; } // =================================================================== // Save/restore the user state // =================================================================== protected static class UserState extends FacesDataIteratorStateManager.BasicState { private static final long serialVersionUID = 1L; private HashMap<String, Boolean> visibleDetails; public UserState(Options options) { super(options); } public HashMap<String, Boolean> getVisibleDetails() { return visibleDetails; } public void setVisibleDetails(HashMap<String, Boolean> visibleDetails) { this.visibleDetails = visibleDetails; } @Override public void restoreState(FacesContext context, FacesDataIterator dataIterator, boolean fullState) { super.restoreState(context, dataIterator, fullState); if(fullState) { UIDataSourceIterator s = (UIDataSourceIterator)dataIterator; s.visibleDetails = getVisibleDetails(); } } @Override public void saveState(FacesContext context, FacesDataIterator dataIterator) { super.saveState(context, dataIterator); UIDataSourceIterator s = (UIDataSourceIterator)dataIterator; setVisibleDetails(s.visibleDetails); } } public FacesDataIterator getFacesDataIterator(FacesContext context) { return this; } public State createDataIteratorState(FacesContext context, Options options) { return new UserState(options); } public Options getOptions() { return null; } // =================================================================== // Manage show/hide details // =================================================================== public boolean isDetailVisible(String position) { if(isExpandedDetail()) { return visibleDetails==null || !visibleDetails.containsKey(position); } else { return visibleDetails!=null && visibleDetails.containsKey(position); } } public boolean isDetailVisible(String position, boolean expandedDetails) { if(expandedDetails) { return visibleDetails==null || !visibleDetails.containsKey(position); } else { return visibleDetails!=null && visibleDetails.containsKey(position); } } public void setDetailVisible(String position, boolean visible) { if(isExpandedDetail()) { if(visible) { removePosition(position); } else { putPosition(position); } } else { if(visible) { putPosition(position); } else { removePosition(position); } } } private void removePosition(String position) { if(visibleDetails!=null) { visibleDetails.remove(position); if(visibleDetails.isEmpty()) { visibleDetails = null; } } } private void putPosition(String position) { if(visibleDetails==null) { visibleDetails = new HashMap<String, Boolean>(); } visibleDetails.put(position,Boolean.TRUE); } public void clearDetailVisiblePositions(){ visibleDetails = null; } public void toggleDetailVisible(String position) { setDetailVisible(position, !isDetailVisible(position)); } public String getToggledVisibleDetail() { return _toggledVisibleDetail; } /* (non-Javadoc) * @see com.ibm.xsp.extlib.component.data.FacesDataIteratorAjax#getAjaxContainerClientId(javax.faces.context.FacesContext) */ public String getAjaxContainerClientId(FacesContext context) { throw new UnsupportedOperationException("This method must be overridden in a subclass."); // $NLX-UIDataSourceIterator.Thismethodmustbeoverriddeninasubc-1$ } // =================================================================== // Faces Component Methods // =================================================================== public void initBeforeContents(FacesContext context) throws FacesException { // Nothing } public void buildContents(FacesContext context, FacesComponentBuilder builder) throws FacesException { // Standard stuff builder.buildAll(context, this, true); } public void initAfterContents(FacesContext context) throws FacesException { //Do nothing, the default implementation } // =================================================================== // Events and life cycle management. // 1- Manages the iterator specific events // 2- Manages the embedded data source // =================================================================== @Override public void queueEvent(FacesEvent event) { if ((event instanceof ToggleRowEvent) || (event instanceof ToggleDetailEvent)) { if (isPartialExecute()) { event.setPhaseId(PhaseId.APPLY_REQUEST_VALUES); } else { event.setPhaseId(PhaseId.INVOKE_APPLICATION); } // We don't need to wrap it as this is just a command action to the table super.queueEvent(event); } else { // event = new FacesEventWrapper(this, event); super.queueEvent(event); } } /* (non-Javadoc) * @see com.ibm.xsp.component.UIDataIterator#publishControlData(javax.faces.context.FacesContext) */ @Override protected List<ShadowedObject> publishControlData(FacesContext context) { List<ShadowedObject> shadowed = super.publishControlData(context); DataSource dataSource = getDataSource(); if( null != dataSource ){ // make the data source "var" available to computed values in the // contained children and facet controls. DataPublisher publisher = ((FacesContextEx)context).getDataPublisher(); List<ShadowedObject> extraShadowed = publisher.pushDataSource(this, dataSource); if( null != shadowed ){ shadowed.addAll( extraShadowed ); }else{ shadowed = extraShadowed; } } return shadowed; } /* (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) { DataSource dataSource = getDataSource(); if( null != dataSource ){ // revoke the "var" value so outer variables with the same variable // name are restored. DataPublisher publisher = ((FacesContextEx)context).getDataPublisher(); publisher.popDataSource(shadowedObjects, this, dataSource); } super.revokeControlData(shadowedObjects, context); } @Override public void broadcast(FacesEvent event) throws AbortProcessingException { if (event instanceof ToggleRowEvent) { ToggleRowEvent ev = (ToggleRowEvent)event; DataModel dataModel = getDataModel(); //>tmg:a11y FacesContext context = getFacesContext(); //<tmg:a11y if(dataModel instanceof TabularDataModel){ TabularDataModel model = (TabularDataModel)dataModel; if (ev.isExpand()) { model.expandRow(ev.getPosition()); } else { model.collapseRow(ev.getPosition()); } //>tmg:a11y String focusClientId = ev.getClientId(); if( null != focusClientId ){ HtmlUtil.storeEncodeParameter(context, this, TOGGLE_ACTION_CLIENT_ID, focusClientId); } //<tmg:a11y } // Tell JSF to switch to render response, like regular commands context.renderResponse(); } else if (event instanceof ToggleDetailEvent) { ToggleDetailEvent ev = (ToggleDetailEvent)event; //>tmg:a11y FacesContext context = getFacesContext(); //<tmg:a11y String[] pos = ev.getTogglePositions(); for(int i=0; i<pos.length; i++) { if(StringUtil.isNotEmpty(pos[i])) { toggleDetailVisible(pos[i]); _toggledVisibleDetail = pos[i]; } } //>tmg:a11y String focusClientId = ev.getClientId(); if( null != focusClientId ){ HtmlUtil.storeEncodeParameter(context, this, TOGGLE_ACTION_CLIENT_ID, focusClientId); } // else (SPR # GCUNA6LJST) can be null when detailsOnClient=true, // as the state toggle action may have been some time ago on the client, // and so it shouldn't output the script to cause focus to move // to the show/hide details icon. //<tmg:a11y // Tell JSF to switch to render response, like regular commands context.renderResponse(); } else if (event instanceof ToggleSortColumnEvent) { ToggleSortColumnEvent ev = (ToggleSortColumnEvent)event; DataModel dm = getDataModel(); //>tmg:a11y FacesContext context = getFacesContext(); //<tmg:a11y if(dm instanceof TabularDataModel) { TabularDataModel tbm = (TabularDataModel)dm; String previouslySortedColumn = tbm.getResortColumn(); if( StringUtil.isNotEmpty(previouslySortedColumn) && !StringUtil.equals(ev.getColumnName(), previouslySortedColumn) ) { // SPR# BGLN85FFWC sort state only maintained on current column tbm.resetResortState( previouslySortedColumn ); } if (tbm.getResortType(ev.getColumnName()) == TabularDataModel.RESORT_ASCENDING) tbm.setResortOrder(ev.getColumnName(), TabularDataModel.SORT_ASCENDING); else if (tbm.getResortType(ev.getColumnName()) == TabularDataModel.RESORT_DESCENDING) tbm.setResortOrder(ev.getColumnName(), TabularDataModel.SORT_DESCENDING); else tbm.setResortOrder(ev.getColumnName(), TabularDataModel.SORT_TOGGLE); //>tmg:a11y String focusClientId = ev.getClientId(); if( null != focusClientId ){ HtmlUtil.storeEncodeParameter(context, this, TOGGLE_ACTION_CLIENT_ID, focusClientId); } //<tmg:a11y } // Tell JSF to switch to render response, like regular commands context.renderResponse(); } else { super.broadcast(event); } } @Override public void encodeEnd(FacesContext context) throws IOException { super.encodeEnd(context); _toggledVisibleDetail = null; // Reset, as it had been rendered... } }