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

org.apache.myfaces.trinidad.component.UIXIterator Maven / Gradle / Ivy

The newest version!
// WARNING: This file was automatically generated. Do not edit it directly,
//          or you will lose your changes.

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.myfaces.trinidad.component;

import java.io.IOException;
import java.util.AbstractMap;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.faces.component.UIComponent;
import javax.faces.component.visit.VisitCallback;
import javax.faces.component.visit.VisitContext;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.PhaseId;
import javax.faces.render.Renderer;
import org.apache.myfaces.trinidad.bean.FacesBean;
import org.apache.myfaces.trinidad.bean.PropertyKey;
import org.apache.myfaces.trinidad.context.RequestContext;
import org.apache.myfaces.trinidad.model.CollectionModel;
import org.apache.myfaces.trinidad.model.LocalRowKeyIndex;
import org.apache.myfaces.trinidad.model.ModelUtils;
import org.apache.myfaces.trinidad.render.ClientRowKeyManager;
import org.apache.myfaces.trinidad.util.ComponentUtils;

/**
 *
 * 

UIXIterator is a component that performs iteration over its child components. It is similar to UIXTable * but has no chrome. * Each child is repeatedly stamped as many times as necessary. * Iteration is done starting at the index given by the "first" attribute, * for as many indices as specified by the "rows" attribute. * If "rows" returns 0, then the iteration continues until * there are no more elements in the underlying data. *

*

* While the <tr:forEach> * will be sufficient for most user's needs, it does not work with a JSF * DataModel, or CollectionModel. It also cannot be bound to EL expressions that * use component-managed EL variables * (such as the "var" variable on an <tr:table>), because * a forEach tag runs during * The <tr:iterator> tag was created to * address these issues. *

*

* To list all, the benefits of UIXIterator over forEach: *

    *
  • Access to component-managed EL variables
  • *
  • Full support for CollectionModel and DataModel
  • *
  • Does not require creating multiple copies of children, * so more memory efficient
  • *
  • Much better at dealing with adding and deleting children, * at least when used with a CollectionModel with a good * implementation of getRowKey()
  • *
  • Supports "binding", and all other forms of JSF component * manipulation
  • *
* and the negative aspects: *
    *
  • Leaves behind a component in the hierarchy, which causes * problems with components like panelFormLayout that try to handle each child * individually.
  • *
  • Because there's only one of each child, the same limitations * on "binding", etc., as apply inside a table also apply to iterator.
  • *
*

*

By default, it processes up to 25 rows. Use the rows attribute to alter this behavior.

* *

Events:

* * * * * * * * * * * *
TypePhasesDescription
org.apache.myfaces.trinidad.event.AttributeChangeEventInvoke
Application
Apply
Request
Values
Event delivered to describe an attribute change. Attribute change events are not delivered for any programmatic change to a property. They are only delivered when a renderer changes a property without the application's specific request. An example of an attribute change event might include the width of a column that supported client-side resizing.
*/ public class UIXIterator extends UIXCollection implements LocalRowKeyIndex, FlattenedComponent { static public final FacesBean.Type TYPE = new FacesBean.Type( UIXCollection.TYPE); static public final PropertyKey VAR_STATUS_KEY = TYPE.registerKey("varStatus", String.class, PropertyKey.CAP_NOT_BOUND); static public final PropertyKey VALUE_KEY = TYPE.registerKey("value"); static public final PropertyKey ROWS_KEY = TYPE.registerKey("rows", Integer.class, Integer.valueOf(25)); static public final PropertyKey FIRST_KEY = TYPE.registerKey("first", Integer.class, Integer.valueOf(0)); static public final String COMPONENT_FAMILY = "org.apache.myfaces.trinidad.Iterator"; static public final String COMPONENT_TYPE = "org.apache.myfaces.trinidad.Iterator"; /** * Construct an instance of the UIXIterator. */ public UIXIterator() { super(null); } /** * Override to return true. */ @Override public boolean getRendersChildren() { return true; } /** * Sets up the iteration context for each child and processes it */ public boolean processFlattenedChildren( final FacesContext context, ComponentProcessingContext cpContext, final ComponentProcessor childProcessor, final S callbackContext) throws IOException { boolean processedChildren; setupFlattenedContext(context, cpContext); try { // Mimic what would normally happen in the non-flattening case for encodeBegin(): processFlattenedChildrenBegin(cpContext); setupFlattenedChildrenContext(context, cpContext); try { Runner runner = new IndexedRunner(context, cpContext) { @Override protected void process(UIComponent kid, ComponentProcessingContext cpContext) throws IOException { RequestContext requestContext = cpContext.getRequestContext(); requestContext.pushCurrentComponent(context, kid); kid.pushComponentToEL(context, null); try { childProcessor.processComponent(context, cpContext, kid, callbackContext); } finally { kid.popComponentFromEL(context); requestContext.popCurrentComponent(context, kid); } } }; processedChildren = runner.run(); Exception exp = runner.getException(); if (exp != null) { if (exp instanceof RuntimeException) throw (RuntimeException) exp; if (exp instanceof IOException) throw (IOException) exp; throw new IllegalStateException(exp); } } finally { tearDownFlattenedChildrenContext(context, cpContext); } } finally { tearDownFlattenedContext(context, cpContext); } return processedChildren; } /** * Returns true if this FlattenedComponent is currently flattening its children * @param context FacesContext * @return true if this FlattenedComponent is currently flattening its children */ public boolean isFlatteningChildren(FacesContext context) { // if we don't have a Renderer, then we're flattening return (getRendererType() == null); } /** * Repeatedly render the children as many times as needed. */ @Override public void encodeChildren(final FacesContext context) throws IOException { if (!isRendered()) return; // if this is the table there will be a rendererType: if (getRendererType() != null) { Renderer renderer = getRenderer(context); if (renderer != null) { renderer.encodeChildren(context, this); } } else // this is not the table. it must be the iterator { Runner runner = new IndexedRunner(context) { @Override protected void process( UIComponent kid, ComponentProcessingContext cpContext ) throws IOException { kid.encodeAll(context); } }; runner.run(); Exception exp = runner.getException(); if (exp != null) { if (exp instanceof RuntimeException) throw (RuntimeException) exp; if (exp instanceof IOException) throw (IOException) exp; throw new IllegalStateException(exp); } } } /** * Enhances the varStatusMap created by the super class to include:
    *
  • begin - the index of the first row being rendered *
  • first - true if the current row is the first row *
  • count - indicates which iteration this is. This always starts at one, * and increases (by one) as the loop progresses. *
  • step - this is always one. *
*/ @Override protected Map createVarStatusMap() { final Map map = super.createVarStatusMap(); return new AbstractMap() { @Override public Object get(Object key) { // some of these keys are from , ie: // javax.servlet.jsp.jstl.core.LoopTagStatus if ("begin".equals(key)) // from jstl { return Integer.valueOf(getFirst()); } if ("first".equals(key)) // from jstl { boolean isFirst = (getFirst() == getRowIndex()); return Boolean.valueOf(isFirst); } if ("count".equals(key)) // from jstl { int count = getRowIndex() - getFirst() + 1; return Integer.valueOf(count); } if ("step".equals(key)) // from jstl { return Integer.valueOf(1); } return map.get(key); } @Override public Set> entrySet() { return map.entrySet(); } }; } @Override protected CollectionModel createCollectionModel( CollectionModel current, Object value) { CollectionModel model = ModelUtils.toCollectionModel(value); // initialize to -1. we need to do this incase some application logic // changed this index. Also, some JSF1.0 RI classes were initially starting // with a rowIndex of 0. // we need this to be -1 because of name-transformation. model.setRowIndex(-1); assert model.getRowIndex() == -1 : "RowIndex did not reset to -1"; return model; } @Override protected void processFacetsAndChildren( final FacesContext context, final PhaseId phaseId) { Runner runner = new IndexedRunner(context) { @Override protected void process(UIComponent kid, ComponentProcessingContext cpContext) { UIXIterator.this.processComponent(context, kid, phaseId); } }; runner.run(); } // Extract the current row token from the clientId private String _getClientToken(String clientIdPrefix, String cellClientId) { int tokenStartIndex = clientIdPrefix.length() + 1; int tokenEndIndex = cellClientId.indexOf(':', tokenStartIndex); if (tokenEndIndex != -1) { return cellClientId.substring(tokenStartIndex, tokenEndIndex); } else { return null; } } @Override protected boolean visitData( final VisitContext visitContext, final VisitCallback visitCallback) { Collection subtreeIds = visitContext.getSubtreeIdsToVisit(this); // create a special VisitContext that doesn't visit the Facets // of column components since they aren't visited on each row final VisitContext noColumnFacetContext = new NoColumnFacetsVisitContext(visitContext); // runner to use to process the rows Runner runner; if (VisitContext.ALL_IDS.equals(subtreeIds)) { // we're processing all of the rows, so use the indexed runner (plus, we can't call size() on // the ALL_IDS collection, so we don't have a whole lot of choice here runner = new IndexedRunner(visitContext.getFacesContext()) { @Override protected void process(UIComponent kid, ComponentProcessingContext cpContext) { if (UIXComponent.visitTree(noColumnFacetContext, kid, visitCallback)) { throw new AbortProcessingException(); } } }; } else { // We are only visiting a subset of the tree, so figure out which rows to visit String ourClientIdPrefix = getClientId(visitContext.getFacesContext()); int subtreeIdCount = subtreeIds.size(); // build up a set of the row keys to visit rather than iterating // and visiting every row Set rowsToVisit; if (subtreeIdCount > 1) { rowsToVisit = new HashSet(subtreeIdCount); for (String currClientId : subtreeIds) { String clientToken = _getClientToken(ourClientIdPrefix, currClientId); if (clientToken != null) { rowsToVisit.add(clientToken); } } } else { String clientToken; if (subtreeIds.iterator().hasNext()) { clientToken = _getClientToken(ourClientIdPrefix, subtreeIds.iterator().next()); } else { clientToken = null; } if (clientToken != null) { rowsToVisit = Collections.singleton(clientToken); } else { rowsToVisit = Collections.emptySet(); } } // we didn't visit any data if (rowsToVisit.isEmpty()) return false; // visit only the rows we need to runner = new KeyedRunner(visitContext.getFacesContext(), rowsToVisit) { @Override protected void process( UIComponent kid, ComponentProcessingContext cpContext ) throws IOException { if (UIXComponent.visitTree(noColumnFacetContext, kid, visitCallback)) { throw new AbortProcessingException(); } } }; } try { runner.run(); } finally { return (runner.getException() instanceof AbortProcessingException); } } /** * Abstract class for processing rows */ private abstract class Runner implements ComponentProcessor { public Runner(FacesContext context) { this(context, null); } public Runner(FacesContext context, ComponentProcessingContext cpContext) { _context = context; _cpContext = cpContext; } public abstract boolean run(); /** * Sets up the context for the child and processes it */ public void processComponent( FacesContext context, ComponentProcessingContext cpContext, UIComponent component, Object callbackContext) throws IOException { try { process(component, cpContext); } catch (IOException ioe) { throw ioe; } catch (AbortProcessingException ape) { // we're done, so abort _exception = ape; throw ape; } catch (Exception e) { _exception = e; } } public Exception getException() { return _exception; } protected abstract void process(UIComponent comp, ComponentProcessingContext cpContext) throws Exception; protected final ComponentProcessingContext getComponentProcessingContext() { return _cpContext; } public final FacesContext getFacesContext() { return _context; } public final void setException(Exception e) { _exception = e; } private Exception _exception = null; private final FacesContext _context; private final ComponentProcessingContext _cpContext; } /** * Class for visiting getRows() by index rows starting getFirst() */ private abstract class IndexedRunner extends Runner { public IndexedRunner(FacesContext context) { this(context, null); } public IndexedRunner(FacesContext context, ComponentProcessingContext cpContext) { super(context, cpContext); } public final boolean run() { FacesContext context = getFacesContext(); ComponentProcessingContext cpContext = getComponentProcessingContext(); List stamps = getStamps(); int oldIndex = getRowIndex(); int first = getFirst(); int rows = getRows(); int end = (rows <= 0) //show everything ? Integer.MAX_VALUE : first + rows; boolean processedChild = false; try { for(int i=first; i clientKeys) { super(context); _clientKeys = clientKeys; } public final boolean run() { FacesContext context = getFacesContext(); List stamps = getStamps(); int oldIndex = getRowIndex(); boolean processedChild = false; try { // need to convert row key tokens to row keys ClientRowKeyManager rowKeyManager = getClientRowKeyManager(); for(String clientKey : _clientKeys) { Object rowKey = rowKeyManager.getRowKey(context, UIXIterator.this, clientKey); if (rowKey != null) { setRowKey(rowKey); if (isRowAvailable()) { // latch processedChild the first time we process a child processedChild |= UIXComponent.processFlattenedChildren(context, this, stamps, null); } } } } catch (IOException e) { setException(e); } finally { setRowIndex(oldIndex); } return processedChild; } private final Iterable _clientKeys; } @Override void __encodeBegin(FacesContext context) throws IOException { _fixupFirst(); super.__encodeBegin(context); } // make sure the current range exists on the model: // see bug 4143852: private void _fixupFirst() { int first = getFirst(); // if we are starting from row zero then there is no problem: if (first == 0) return; // Negative "first" makes no sense. Given the logic below, // it forces iterator to scroll to the end unnecessarily. if (first < 0) { setFirst(0); return; } CollectionModel model = getCollectionModel(); int oldIndex = model.getRowIndex(); try { model.setRowIndex(first); // if the starting row doesn't exist then we need to scroll back: if (!model.isRowAvailable()) { int size = model.getRowCount(); int rows = getRows(); // if the rowCount is unknown OR // the blockSize is show all OR // there are fewer rows than the blockSize on the table // then start from the beginning: if ((size <= 0) || (rows <= 0) || (size <= rows)) first = 0; else { // scroll to the last page: first = size - rows; model.setRowIndex(first); // make sure the row is indeed available: if (!model.isRowAvailable()) { // row is not available. this happens when getRowCount() lies. // Some DataModel implementations seem to have rowCount methods which // lie. see bug 4157186 first = 0; } } setFirst(first); } } finally { model.setRowIndex(oldIndex); } } /** * Gets * Name of the EL variable used to reference the varStatus information. * Once this component has completed rendering, this variable is * removed (or reverted back to its previous value). * The VarStatus provides contextual information about the state of the * component to EL expressions. For components that iterate, varStatus * also provides loop counter information. Please see the this * component's documentation for the specific properties on the varStatus. * The common properties on varStatus include: *
    *
  • "model" - returns the CollectionModel for this component.
  • *
  • "index" - returns the zero based row index.
  • *
  • "hierarchicalIndex" - returns an array containing zero based row index.
  • *
  • "hierarchicalLabel" - returns a string label representing 1 based index of this row.
  • *
  • "rowKey" - returns the current rowKey in the collection.
  • *
  • "current" - returns the current row in the collection.
  • *
* * @return the new varStatus value */ final public String getVarStatus() { return ComponentUtils.resolveString(getProperty(VAR_STATUS_KEY)); } /** * Sets * Name of the EL variable used to reference the varStatus information. * Once this component has completed rendering, this variable is * removed (or reverted back to its previous value). * The VarStatus provides contextual information about the state of the * component to EL expressions. For components that iterate, varStatus * also provides loop counter information. Please see the this * component's documentation for the specific properties on the varStatus. * The common properties on varStatus include: *
    *
  • "model" - returns the CollectionModel for this component.
  • *
  • "index" - returns the zero based row index.
  • *
  • "hierarchicalIndex" - returns an array containing zero based row index.
  • *
  • "hierarchicalLabel" - returns a string label representing 1 based index of this row.
  • *
  • "rowKey" - returns the current rowKey in the collection.
  • *
  • "current" - returns the current row in the collection.
  • *
* * @param varStatus the new varStatus value */ final public void setVarStatus(String varStatus) { setProperty(VAR_STATUS_KEY, (varStatus)); } /** * Gets the data model being used by this component. * The specific model class is * org.apache.myfaces.trinidad.model.CollectionModel. * * You may also use other model instances, e.g., * java.util.List , * array, and javax.faces.model.DataModel. * This component will automatically convert the instance * into a CollectionModel. * * @return the new value value */ final public Object getValue() { return getProperty(VALUE_KEY); } /** * Sets the data model being used by this component. * The specific model class is * org.apache.myfaces.trinidad.model.CollectionModel. * * You may also use other model instances, e.g., * java.util.List , * array, and javax.faces.model.DataModel. * This component will automatically convert the instance * into a CollectionModel. * * @param value the new value value */ final public void setValue(Object value) { setProperty(VALUE_KEY, (value)); } /** * Gets the maximum number of rows to display in a single range of rows. * Some ranges might have fewer * than the number of rows specified by this attribute (eg: the last range * might have an insufficient number of rows). * To display all rows at once, set this attribute to 0. * The default is 25. * * @return the new rows value */ final public int getRows() { return ComponentUtils.resolveInteger(getProperty(ROWS_KEY), 25); } /** * Sets the maximum number of rows to display in a single range of rows. * Some ranges might have fewer * than the number of rows specified by this attribute (eg: the last range * might have an insufficient number of rows). * To display all rows at once, set this attribute to 0. * The default is 25. * * @param rows the new rows value */ final public void setRows(int rows) { setProperty(ROWS_KEY, Integer.valueOf(rows)); } /** * Gets the index of the first row in the currently range of rows. * This index is zero-based. This attribute is used to control * which range of rows to display to the user. * * @return the new first value */ final public int getFirst() { return ComponentUtils.resolveInteger(getProperty(FIRST_KEY), 0); } /** * Sets the index of the first row in the currently range of rows. * This index is zero-based. This attribute is used to control * which range of rows to display to the user. * * @param first the new first value */ final public void setFirst(int first) { setProperty(FIRST_KEY, Integer.valueOf(first)); } @Override public String getFamily() { return COMPONENT_FAMILY; } @Override protected FacesBean.Type getBeanType() { return TYPE; } /** * Construct an instance of the UIXIterator. */ protected UIXIterator( String rendererType ) { super(rendererType); } static { TYPE.lock(); } }