/*
* JBoss, Home of Professional Open Source
* Copyright ${year}, Red Hat, Inc. and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.richfaces.sandbox.component;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.el.ELException;
import javax.el.MethodExpression;
import javax.el.ValueExpression;
import javax.faces.application.Application;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.UpdateModelException;
import javax.faces.component.visit.VisitCallback;
import javax.faces.component.visit.VisitContext;
import javax.faces.component.visit.VisitResult;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ExceptionQueuedEvent;
import javax.faces.event.ExceptionQueuedEventContext;
import javax.faces.event.FacesEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PostValidateEvent;
import javax.faces.event.PreValidateEvent;
import org.richfaces.cdk.annotations.Attribute;
import org.richfaces.cdk.annotations.EventName;
import org.richfaces.cdk.annotations.JsfComponent;
import org.richfaces.cdk.annotations.JsfRenderer;
import org.richfaces.cdk.annotations.Tag;
import org.richfaces.cdk.annotations.TagType;
import org.richfaces.context.ExtendedVisitContext;
import org.richfaces.context.ExtendedVisitContextMode;
import org.richfaces.l10n.FacesMessages;
import org.richfaces.l10n.MessageFactory;
import org.richfaces.model.DataVisitResult;
import org.richfaces.model.DataVisitor;
import org.richfaces.services.ServiceTracker;
import org.richfaces.ui.common.SwitchType;
import org.richfaces.ui.common.meta.MetaComponentEncoder;
import org.richfaces.ui.common.meta.MetaComponentRenderer;
import org.richfaces.ui.common.meta.MetaComponentResolver;
import org.richfaces.ui.iteration.repeat.UIRepeat;
import org.richfaces.ui.toggle.ItemChangeEvent;
import org.richfaces.ui.toggle.ItemChangeListener;
import org.richfaces.ui.toggle.ItemChangeSource;
import org.richfaces.util.MessageUtil;
import org.richfaces.util.RendererUtils;
import com.google.common.base.Strings;
/**
* <p>The <rich:togglePanel> component is used as a base for the other switchable components, the
* <rich:accordion> component and the <rich:tabPanel> component. It provides an abstract switchable
* component without any associated markup. As such, the <rich:togglePanel> component could be customized to
* provide a switchable component when neither an accordion component or a tab panel component is appropriate.</p>
*
* @author akolonitsky
*/
@JsfComponent(tag = @Tag(type = TagType.Facelets, handler = "org.richfaces.sandbox.view.facelets.html.TogglePanelTagHandler"), renderer = @JsfRenderer(type = "org.richfaces.sandbox.TogglePanelRenderer"), attributes = {
"core-props.xml", "events-mouse-props.xml", "i18n-props.xml" })
public abstract class AbstractTogglePanel extends UIRepeat implements AbstractDivPanel, ItemChangeSource, MetaComponentResolver, MetaComponentEncoder {
public static final String ACTIVE_ITEM_META_COMPONENT = "activeItem";
public static final String COMPONENT_TYPE = "org.richfaces.sandbox.TogglePanel";
public static final String COMPONENT_FAMILY = "org.richfaces.sandbox.TogglePanel";
public static final String META_NAME_FIRST = "@first";
public static final String META_NAME_PREV = "@prev";
public static final String META_NAME_NEXT = "@next";
public static final String META_NAME_LAST = "@last";
// TODO What is MessageId ?
public static final String UPDATE_MESSAGE_ID = "javax.faces.component.UIInput.UPDATE";
private String submittedActiveItem = null;
private enum PropertyKeys {
activeItem, localActiveItemSet, required, valid, immediate, switchType
}
protected AbstractTogglePanel() {
setRendererType("org.richfaces.sandbox.TogglePanelRenderer");
decodeVisitor = new ComponentVisitor() {
@Override
public void processComponent(FacesContext context, UIComponent c, Object argument)
{
if (isActiveItem(c) || getSwitchType() == SwitchType.client) {
c.processDecodes(context);
}
}
};
validateVisitor = new ComponentVisitor() {
@Override
public void processComponent(FacesContext context, UIComponent c, Object argument)
{
if (isActiveItem(c) || getSwitchType() == SwitchType.client) {
c.processValidators(context);
}
}
};
updateVisitor = new ComponentVisitor() {
@Override
public void processComponent(FacesContext context, UIComponent c, Object argument)
{
if (isActiveItem(c) || getSwitchType() == SwitchType.client) {
c.processUpdates(context);
}
}
};
}
// -------------------------------------------------- Editable Value Holder
public String getSubmittedActiveItem() {
return this.submittedActiveItem;
}
public void resetValue() {
this.setValue(null);
this.setSubmittedActiveItem(null);
this.setLocalActiveItemSet(false);
this.setValid(true);
}
public void setSubmittedActiveItem(Object submittedValue) {
this.submittedActiveItem = String.valueOf(submittedValue);
}
public boolean isLocalActiveItemSet() {
return (Boolean) getStateHelper().eval(PropertyKeys.localActiveItemSet, false);
}
public void setLocalActiveItemSet(boolean localValueSet) {
getStateHelper().put(PropertyKeys.localActiveItemSet, localValueSet);
}
public boolean isValid() {
return (Boolean) getStateHelper().eval(PropertyKeys.valid, true);
}
public void setValid(boolean valid) {
getStateHelper().put(PropertyKeys.valid, valid);
}
public boolean isRequired() {
return (Boolean) getStateHelper().eval(PropertyKeys.required, false);
}
/**
* <p>
* Set the "required field" state for this component.
* </p>
*
* @param required The new "required field" state
*/
public void setRequired(boolean required) {
getStateHelper().put(PropertyKeys.required, required);
}
/**
* Flag indicating that this component's value must be converted and validated immediately (that is, during Apply Request
* Values phase), rather than waiting until Process Validations phase.
*/
@Attribute
public boolean isImmediate() {
return (Boolean) getStateHelper().eval(PropertyKeys.immediate, false);
}
public void setImmediate(boolean immediate) {
getStateHelper().put(PropertyKeys.immediate, immediate);
}
// ----------------------------------------------------- UIComponent Methods
@Override
public void encodeBegin(FacesContext context) throws IOException {
if (context == null) {
throw new NullPointerException();
}
pushComponentToEL(context, null);
if (!isRendered()) {
return;
}
AbstractTogglePanelItemInterface item = null;
String activeItem = getActiveItem();
if (!Strings.isNullOrEmpty(activeItem)) {
item = this.getItem(activeItem);
}
if (item == null || !((UIComponent) item).isRendered()) {
if (getValue() != null) {
try {
DataVisitor visitor = new AbstractTogglePanelItemVisitor(this, new AbstractTogglePanelItemVisitor.TabVisitorCallback() {
@Override
public DataVisitResult visit(AbstractTogglePanelItemInterface item)
{
setActiveItem(item.getName());
return DataVisitResult.STOP;
}
});
walk(context, visitor, null);
} finally {
setRowKey(context, null);
}
} else {
List<AbstractTogglePanelItemInterface> renderedItems = this.getRenderedItems();
if (!renderedItems.isEmpty()) {
setActiveItem(renderedItems.get(0).getName());
}
}
}
super.encodeBegin(context);
}
/**
* <p>
* Specialized decode behavior on top of that provided by the superclass. In addition to the standard
* <code>processDecodes</code> behavior inherited from {@link javax.faces.component.UIComponentBase}, calls
* <code>processValue()</code> if the the <code>immediate</code> property is true; if the component is invalid afterwards or
* a <code>RuntimeException</code> is thrown, calls {@link FacesContext#renderResponse}.
* </p>
*
* @throws NullPointerException {@inheritDoc}
*/
@Override
public void processDecodes(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
// Skip processing if our rendered flag is false
if (!isRendered()) {
return;
}
pushComponentToEL(context, null);
// Process all facets and children of this component
Iterator<UIComponent> kids = getFacetsAndChildren();
String activeItem = getActiveItemValue();
while (kids.hasNext()) {
UIComponent kid = kids.next();
if (isActiveItem(kid, activeItem) || this.getSwitchType() == SwitchType.client) {
kid.processDecodes(context);
}
}
super.processDecodes(context);
// Process this component itself
try {
decode(context);
} catch (RuntimeException e) {
context.renderResponse();
throw e;
} finally {
popComponentFromEL(context);
}
ItemChangeEvent event = createItemChangeEvent(context);
if (event != null) {
event.queue();
}
}
/**
* <p>
* In addition to the standard <code>processValidators</code> behavior inherited from
* {@link javax.faces.component.UIComponentBase}, calls <code>processValue()</code> if the <code>immediate</code> property
* is false (which is the default); if the component is invalid afterwards, calls {@link FacesContext#renderResponse}. If a
* <code>RuntimeException</code> is thrown during validation processing, calls {@link FacesContext#renderResponse} and
* re-throw the exception.
* </p>
*
* @throws NullPointerException {@inheritDoc}
*/
@Override
public void processValidators(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
// Skip processing if our rendered flag is false
if (!isRendered()) {
return;
}
pushComponentToEL(context, null);
Application app = context.getApplication();
app.publishEvent(context, PreValidateEvent.class, this);
// Process all the facets and children of this component
Iterator<UIComponent> kids = getFacetsAndChildren();
String activeItem = getActiveItemValue();
while (kids.hasNext()) {
UIComponent kid = kids.next();
if (isActiveItem(kid, activeItem) || this.getSwitchType() == SwitchType.client) {
kid.processValidators(context);
}
}
// TODO process validators
super.processValidators(context);
app.publishEvent(context, PostValidateEvent.class, this);
popComponentFromEL(context);
}
/**
* <p>
* In addition to the standard <code>processUpdates</code> behavior inherited from
* {@link javax.faces.component.UIComponentBase}, calls <code>updateModel()</code>. If the component is invalid afterwards,
* calls {@link FacesContext#renderResponse}. If a <code>RuntimeException</code> is thrown during update processing, calls
* {@link FacesContext#renderResponse} and re-throw the exception.
* </p>
*
* @throws NullPointerException {@inheritDoc}
*/
@Override
public void processUpdates(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
// Skip processing if our rendered flag is false
if (!isRendered()) {
return;
}
pushComponentToEL(context, null);
// Process all facets and children of this component
Iterator<UIComponent> kids = getFacetsAndChildren();
String activeItem = getActiveItemValue();
while (kids.hasNext()) {
UIComponent kid = kids.next();
if (isActiveItem(kid, activeItem) || this.getSwitchType() == SwitchType.client) {
kid.processUpdates(context);
}
}
// TODO processUpdates
super.processUpdates(context);
popComponentFromEL(context);
if (!isValid()) {
context.renderResponse();
}
}
/**
* @throws NullPointerException {@inheritDoc}
*/
@Override
public void decode(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
// Force validity back to "true"
setValid(true);
super.decode(context);
}
public void updateModel(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
if (!isValid() || !isLocalActiveItemSet()) {
return;
}
ValueExpression ve = getValueExpression("activeItem");
if (ve == null) {
return;
}
Throwable caught = null;
FacesMessage message = null;
try {
ve.setValue(context.getELContext(), getLocalValue());
setActiveItem(null);
setLocalActiveItemSet(false);
} catch (ELException e) {
caught = e;
String messageStr = e.getMessage();
Throwable result = e.getCause();
while (null != result && result.getClass().isAssignableFrom(ELException.class)) {
messageStr = result.getMessage();
result = result.getCause();
}
if (messageStr == null) {
message = ServiceTracker.getService(MessageFactory.class).createMessage(context, FacesMessage.SEVERITY_ERROR,
FacesMessages.UIINPUT_UPDATE, MessageUtil.getLabel(context, this));
} else {
message = new FacesMessage(FacesMessage.SEVERITY_ERROR, messageStr, messageStr);
}
setValid(false);
} catch (Exception e) {
caught = e;
// message = MessageFactory.getMessage(context, UPDATE_MESSAGE_ID,
// MessageFactory.getHeader(context, this));
setValid(false);
}
if (caught != null) {
assert message != null;
@SuppressWarnings({ "ThrowableInstanceNeverThrown" })
UpdateModelException toQueue = new UpdateModelException(message, caught);
ExceptionQueuedEventContext eventContext = new ExceptionQueuedEventContext(context, toQueue, this,
PhaseId.UPDATE_MODEL_VALUES);
context.getApplication().publishEvent(context, ExceptionQueuedEvent.class, eventContext);
}
}
@Attribute
public abstract Object getLocalValue();
private ItemChangeEvent createItemChangeEvent(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
// Submitted value == null means "the component was not submitted at all".
String activeItem = getSubmittedActiveItem();
if (activeItem == null) {
return null;
}
String previous = getActiveItem();
if (previous == null || !previous.equalsIgnoreCase(activeItem)) {
UIComponent prevComp = null;
UIComponent actvComp = null;
if (previous != null) {
prevComp = (UIComponent) getItem(previous);
}
if (activeItem != null) {
actvComp = (UIComponent) getItem(activeItem);
}
return new ItemChangeEvent(this, previous, prevComp, activeItem, actvComp);
}
return null;
}
@Override
public void queueEvent(FacesEvent event) {
if ((event instanceof ItemChangeEvent) && (event.getComponent() == this)) {
setEventPhase((ItemChangeEvent) event);
}
super.queueEvent(event);
}
protected void setEventPhase(ItemChangeEvent event) {
if (isImmediate()
|| (event.getNewItem() != null && RendererUtils.getInstance().isBooleanAttribute(event.getNewItem(),
"immediate"))) {
event.setPhaseId(PhaseId.APPLY_REQUEST_VALUES);
} else {
event.setPhaseId(PhaseId.UPDATE_MODEL_VALUES);
}
}
protected void setEventPhase(FacesEvent event) {
if (isImmediate()) {
event.setPhaseId(PhaseId.APPLY_REQUEST_VALUES);
} else {
event.setPhaseId(PhaseId.INVOKE_APPLICATION);
}
}
@Override
public void broadcast(FacesEvent event) throws AbortProcessingException {
FacesContext context = FacesContext.getCurrentInstance();
if (event instanceof ItemChangeEvent) {
setActiveItem(((ItemChangeEvent) event).getNewItemName());
setSubmittedActiveItem(null);
if (event.getPhaseId() == PhaseId.UPDATE_MODEL_VALUES) {
try {
updateModel(context);
} catch (RuntimeException e) {
context.renderResponse();
throw e;
}
} else {
context.renderResponse();
}
}
super.broadcast(event);
}
// -------------------------------------------------- Panel Items Managing
@Override
public String getFamily() {
return COMPONENT_FAMILY;
}
@Override
public boolean getRendersChildren() {
return true;
}
private String getActiveItemValue() {
String value = getActiveItem();
if (value == null) {
value = getSubmittedActiveItem();
}
return value;
}
protected boolean isActiveItem(UIComponent kid) {
return isActiveItem(kid, getActiveItemValue());
}
protected boolean isActiveItem(UIComponent kid, String value) {
if (kid == null || value == null) {
return false;
}
return getChildName(kid).equals(value);
}
private static String getChildName(UIComponent item) {
if (item == null) {
return null;
}
if (!(item instanceof AbstractTogglePanelItemInterface)) {
throw new IllegalArgumentException();
}
return ((AbstractTogglePanelItemInterface) item).getName();
}
public AbstractTogglePanelItemInterface getItemByIndex(final int index) {
// TODO rewrite this method
if(getValue() != null) {
// TODO find out smarter way to calculate number of tabs
final Map<Integer,Object> index2rowKey = new HashMap<Integer,Object>();
final int[] sizeHolder = new int[1];
DataVisitor visitor = new AbstractTogglePanelItemVisitor(this, new AbstractTogglePanelItemVisitor.TabVisitorCallback() {
@Override
public DataVisitResult visit(AbstractTogglePanelItemInterface item)
{
index2rowKey.put(sizeHolder[0],getRowKey());
sizeHolder[0]++;
return DataVisitResult.CONTINUE;
}
});
walk(FacesContext.getCurrentInstance(), visitor, null);
if (index < 0 || index >= sizeHolder[0]) {
return null;
}
int size = getRenderedItems().size();
setRowKey(index2rowKey.get((size + index) % size));
List<AbstractTogglePanelItemInterface> children = getRenderedItems();
int innerIndex = index % size;
if (isCycledSwitching()) {
size = children.size();
return children.get((size + innerIndex) % size);
} else {
return children.get(innerIndex);
}
} else {
List<AbstractTogglePanelItemInterface> children = getRenderedItems();
if (index < 0 || index >= children.size()) {
return null;
} else if (isCycledSwitching()) {
int size = getRenderedItems().size();
return children.get((size + index) % size);
} else {
return children.get(index);
}
}
}
public List<AbstractTogglePanelItemInterface> getRenderedItems() {
return getItems(false);
}
public List<AbstractTogglePanelItemInterface> getItems(boolean isRendered) {
List<AbstractTogglePanelItemInterface> res = new ArrayList<AbstractTogglePanelItemInterface>(getChildCount());
for (UIComponent child : getChildren()) {
if ((isRendered || child.isRendered()) && child instanceof AbstractTogglePanelItemInterface) {
res.add((AbstractTogglePanelItemInterface) child);
}
}
return res;
}
public AbstractTogglePanelItemInterface getItem(String name) {
if (META_NAME_FIRST.equals(name)) {
return getFirstItem();
} else if (META_NAME_PREV.equals(name)) {
return getPrevItem();
} else if (META_NAME_NEXT.equals(name)) {
return getNextItem();
} else if (META_NAME_LAST.equals(name)) {
return getLastItem();
} else {
return getItemByIndex(getChildIndex(name));
}
}
public AbstractTogglePanelItemInterface getFirstItem() {
return getItemByIndex(0);
}
public AbstractTogglePanelItemInterface getPrevItem() {
return getPrevItem(getActiveItem());
}
public AbstractTogglePanelItemInterface getPrevItem(String name) {
return getItemByIndex(getChildIndex(name) - 1);
}
public AbstractTogglePanelItemInterface getNextItem() {
return getNextItem(getActiveItem());
}
public AbstractTogglePanelItemInterface getNextItem(String name) {
return getItemByIndex(getChildIndex(name) + 1);
}
public AbstractTogglePanelItemInterface getLastItem() {
return getItemByIndex(getRenderedItems().size() - 1);
}
public int getChildIndex(final String name) {
if (name == null) {
throw new IllegalArgumentException("Name is required parameter.");
}
// TODO rewrite this
if(getValue()!=null) {
final int[] indexHodler = new int[] {Integer.MIN_VALUE};
final int[] iHodler = new int[1];
DataVisitor visitor = new AbstractTogglePanelItemVisitor(this, new AbstractTogglePanelItemVisitor.TabVisitorCallback() {
@Override
public DataVisitResult visit(AbstractTogglePanelItemInterface item)
{
if (name.equals(item.getName())) {
indexHodler[0] = iHodler[0];
return DataVisitResult.STOP;
}
iHodler[0]++;
return DataVisitResult.CONTINUE;
}
});
walk(FacesContext.getCurrentInstance(), visitor, null);
return indexHodler[0];
} else {
List<AbstractTogglePanelItemInterface> items = getRenderedItems();
for (int ind = 0; ind < items.size(); ind++) {
if (name.equals(items.get(ind).getName())) {
return ind;
}
}
}
return Integer.MIN_VALUE;
}
// ------------------------------------------------
public void setSubmittedActiveItem(String submittedActiveItem) {
this.submittedActiveItem = submittedActiveItem;
}
// ------------------------------------------------ Properties
/**
* Holds the active panel name. This name is a reference to the name identifier of the active child
* <rich:togglePanelItem> component.
*/
@Attribute(generate = false)
public String getActiveItem() {
return (String) getStateHelper().eval(PropertyKeys.activeItem, null);
}
public void setActiveItem(String activeItem) {
getStateHelper().put(PropertyKeys.activeItem, activeItem);
setLocalActiveItemSet(true);
}
@Override
public void setValueExpression(String name, ValueExpression binding) {
if ("activeItem".equals(name)) {
super.setValueExpression("value", binding);
} else {
super.setValueExpression(name, binding);
}
}
/**
* The switch mode when a panel is activated. One of: "client", "server", "ajax". Default: "ajax"
*/
@Attribute(generate = false)
public SwitchType getSwitchType() {
SwitchType switchType = (SwitchType) getStateHelper().eval(PropertyKeys.switchType);
if (switchType == null) {
switchType = SwitchType.DEFAULT;
}
return switchType;
}
public void setSwitchType(SwitchType switchType) {
getStateHelper().put(PropertyKeys.switchType, switchType);
}
@Attribute(hidden = true)
public abstract boolean isLimitRender();
/**
* Applicable when cycling through the tabs. If "true", then when the last tab is active, cycling to next will activate the
* first tab, if "false", cycling to next will have not effect. The inverse applies for the first tab, and cycling to
* previous. Whether to Default: false
*/
@Attribute
public abstract boolean isCycledSwitching();
@Attribute(hidden = true)
public abstract Object getData();
@Attribute(hidden = true)
public abstract String getStatus();
@Attribute(hidden = true)
public abstract Object getExecute();
@Attribute(hidden = true)
public abstract Object getRender();
/**
* Occurs on the server side when an item is changed through Ajax using the server mode
*/
@Attribute
public abstract MethodExpression getItemChangeListener();
/**
* The client-side script method to be called after the item is changed.
*/
@Attribute(events = @EventName("itemchange"))
public abstract String getOnitemchange();
/**
* The client-side script method to be called before the item is changed.
*/
@Attribute(events = @EventName("beforeitemchange"))
public abstract String getOnbeforeitemchange();
// ------------------------------------------------ Event Processing Methods
public void addItemChangeListener(ItemChangeListener listener) {
addFacesListener(listener);
}
public ItemChangeListener[] getItemChangeListeners() {
return (ItemChangeListener[]) getFacesListeners(ItemChangeListener.class);
}
public void removeItemChangeListener(ItemChangeListener listener) {
removeFacesListener(listener);
}
public String resolveClientId(FacesContext facesContext, UIComponent contextComponent, String metaComponentId) {
if (ACTIVE_ITEM_META_COMPONENT.equals(metaComponentId)) {
return getClientId(facesContext) + MetaComponentResolver.META_COMPONENT_SEPARATOR_CHAR + metaComponentId;
}
return null;
}
public String substituteUnresolvedClientId(FacesContext facesContext, UIComponent contextComponent, String metaComponentId) {
return null;
}
public void encodeMetaComponent(FacesContext context, String metaComponentId) throws IOException {
((MetaComponentRenderer) getRenderer(context)).encodeMetaComponent(context, this, metaComponentId);
}
@Override
public boolean visitTree(VisitContext context, VisitCallback callback) {
if (!isVisitable(context)) {
return false;
}
FacesContext facesContext = context.getFacesContext();
pushComponentToEL(facesContext, null);
try {
VisitResult result = context.invokeVisitCallback(this, callback);
if (result == VisitResult.COMPLETE) {
return true;
}
if (result == VisitResult.ACCEPT) {
if (context instanceof ExtendedVisitContext) {
ExtendedVisitContext extendedVisitContext = (ExtendedVisitContext) context;
if (extendedVisitContext.getVisitMode() == ExtendedVisitContextMode.RENDER) {
result = extendedVisitContext.invokeMetaComponentVisitCallback(this, callback,
ACTIVE_ITEM_META_COMPONENT);
if (result == VisitResult.COMPLETE) {
return true;
}
}
}
}
if (result == VisitResult.ACCEPT) {
if (getValue() != null) {
return super.visitTree(context, callback);
} else {
Iterator<UIComponent> kids = this.getFacetsAndChildren();
while (kids.hasNext()) {
boolean done = kids.next().visitTree(context, callback);
if (done) {
return true;
}
}
}
}
} finally {
popComponentFromEL(facesContext);
}
return false;
}
}