/** @author Nick Belaevski */
@ResourceDependencies({
  @ResourceDependency(library = "javax.faces", name = "jsf.js"),
  @ResourceDependency(library = "org.richfaces", name = "jquery.js"),
  @ResourceDependency(library = "org.richfaces", name = "richfaces.js"),
  @ResourceDependency(library = "org.richfaces", name = "richfaces-queue.reslib"),
  @ResourceDependency(library = "org.richfaces", name = "richfaces-base-component.js"),
  @ResourceDependency(library = "org.richfaces", name = "jquery.position.js"),
  @ResourceDependency(library = "org.richfaces", name = "richfaces-event.js"),
  @ResourceDependency(library = "org.richfaces", name = "richfaces-utils.js"),
  @ResourceDependency(library = "org.richfaces", name = "richfaces-selection.js"),
  @ResourceDependency(library = "org.richfaces", name = "AutocompleteBase.js"),
  @ResourceDependency(library = "org.richfaces", name = "Autocomplete.js"),
  @ResourceDependency(library = "org.richfaces", name = "Autocomplete.ecss")
})
public abstract class AutocompleteRendererBase extends InputRendererBase
    implements MetaComponentRenderer {
  private static final Logger LOGGER = RichfacesLogger.RENDERKIT.getLogger();

  public JSReference getClientFilterFunction(UIComponent component) {
    AbstractAutocomplete autocomplete = (AbstractAutocomplete) component;
    String clientFilter = (String) autocomplete.getAttributes().get("clientFilterFunction");
    if (clientFilter != null && clientFilter.length() != 0) {
      return new JSReference(clientFilter);
    }

    return null;
  }

  // TODO nick - handle parameter

  @SuppressWarnings("unchecked")
  private Object saveVar(FacesContext context, String var) {
    if (var != null) {
      Map<String, Object> requestMap = context.getExternalContext().getRequestMap();
      return requestMap.get(var);
    }

    return null;
  }

  private void setVar(FacesContext context, String var, Object varObject) {
    if (var != null) {
      Map<String, Object> requestMap = context.getExternalContext().getRequestMap();
      requestMap.put(var, varObject);
    }
  }

  protected void encodeItems(
      FacesContext facesContext, UIComponent component, List<Object> fetchValues)
      throws IOException {
    AbstractAutocomplete comboBox = (AbstractAutocomplete) component;
    AutocompleteEncodeStrategy strategy = getStrategy(component);
    strategy.encodeItemsContainerBegin(facesContext, component);

    Object savedVar = saveVar(facesContext, comboBox.getVar());
    Map<String, String> requestParameters =
        facesContext.getExternalContext().getRequestParameterMap();
    String value = requestParameters.get(component.getClientId(facesContext) + "Value");
    Iterator<Object> itemsIterator = comboBox.getItems(facesContext, value).iterator();

    if (!itemsIterator.hasNext()) {
      strategy.encodeFakeItem(facesContext, component);
    } else {
      while (itemsIterator.hasNext()) {
        Object item = itemsIterator.next();

        setVar(facesContext, comboBox.getVar(), item);
        this.encodeItem(facesContext, comboBox, item, strategy);

        if (comboBox.getFetchValue() != null) {
          fetchValues.add(comboBox.getFetchValue().toString());
        } else if (item != null) {
          fetchValues.add(item.toString());
        }
      }
    }

    setVar(facesContext, comboBox.getVar(), savedVar);

    strategy.encodeItemsContainerEnd(facesContext, component);
  }

  protected void encodeItemsContainer(FacesContext facesContext, UIComponent component)
      throws IOException {
    AutocompleteEncodeStrategy strategy = getStrategy(component);
    AutocompleteMode mode = (AutocompleteMode) component.getAttributes().get("mode");
    if (mode != null && mode == AutocompleteMode.client) {
      List<Object> fetchValues = new ArrayList<Object>();
      this.encodeItems(facesContext, component, fetchValues);
      this.encodeFetchValues(facesContext, component, fetchValues);
    } else {
      strategy.encodeItemsContainerBegin(facesContext, component);
      // TODO: is it needed
      // strategy.encodeFakeItem(facesContext, component);
      strategy.encodeItemsContainerEnd(facesContext, component);
    }
  }

  private void encodeFetchValues(
      FacesContext facesContext, UIComponent component, List<Object> fetchValues)
      throws IOException {
    if (!fetchValues.isEmpty()) {
      ResponseWriter writer = facesContext.getResponseWriter();
      writer.startElement(HtmlConstants.SCRIPT_ELEM, component);
      writer.writeAttribute(HtmlConstants.TYPE_ATTR, "text/javascript", null);
      JSObject script =
          new JSObject(
              "RichFaces.ui.Autocomplete.setData",
              component.getClientId(facesContext) + "Items",
              fetchValues);
      writer.writeText(script, null);
      writer.endElement(HtmlConstants.SCRIPT_ELEM);
    }
  }

  private int getLayoutChildCount(AbstractAutocomplete component) {
    int count = component.getChildCount();

    for (UIComponent c : component.getChildren()) {
      // do not switch from default strategy if just attachQueue or placeholder is present
      if (c instanceof AbstractAttachQueue || c instanceof AbstractPlaceholder) {
        count -= 1;
      }
    }

    return count;
  }

  public void encodeItem(
      FacesContext facesContext,
      AbstractAutocomplete comboBox,
      Object item,
      AutocompleteEncodeStrategy strategy)
      throws IOException {
    ResponseWriter writer = facesContext.getResponseWriter();
    if (getLayoutChildCount(comboBox) > 0) {
      strategy.encodeItem(facesContext, comboBox);
    } else {
      if (item != null) {
        strategy.encodeItemBegin(facesContext, comboBox);
        writer.writeAttribute(
            HtmlConstants.CLASS_ATTRIBUTE, "rf-au-itm rf-au-opt rf-au-fnt rf-au-inp", null);
        writer.writeText(item, null);
        strategy.encodeItemEnd(facesContext, comboBox);
      }
    }
  }

  private AutocompleteEncodeStrategy getStrategy(UIComponent component) {
    AbstractAutocomplete comboBox = (AbstractAutocomplete) component;
    if (comboBox.getLayout() != null) {
      if (comboBox.getLayout().equals(AutocompleteLayout.div.toString())) {
        return new AutocompleteDivLayoutStrategy();
      }
      if (comboBox.getLayout().equals(AutocompleteLayout.list.toString())) {
        return new AutocompleteListLayoutStrategy();
      }
      if (comboBox.getLayout().equals(AutocompleteLayout.table.toString())) {
        return new AutocompleteTableLayoutStrategy();
      }
    }
    return new AutocompleteDivLayoutStrategy();
  }

  @Override
  protected void doDecode(FacesContext context, UIComponent component) {
    AbstractAutocomplete autocomplete = (AbstractAutocomplete) component;
    if (InputUtils.isDisabled(autocomplete)) {
      return;
    }
    Map<String, String> requestParameters = context.getExternalContext().getRequestParameterMap();
    String value = requestParameters.get(component.getClientId(context) + "Input");
    if (value != null) {
      autocomplete.setSubmittedValue(value);
    }

    if (requestParameters.get(component.getClientId(context) + ".ajax") != null) {
      PartialViewContext pvc = context.getPartialViewContext();
      pvc.getRenderIds()
          .add(
              component.getClientId(context)
                  + MetaComponentResolver.META_COMPONENT_SEPARATOR_CHAR
                  + AbstractAutocomplete.ITEMS_META_COMPONENT_ID);

      context.renderResponse();
    }
  }

  public void encodeMetaComponent(
      FacesContext context, UIComponent component, String metaComponentId) throws IOException {
    if (AbstractAutocomplete.ITEMS_META_COMPONENT_ID.equals(metaComponentId)) {

      List<Object> fetchValues = new ArrayList<Object>();

      PartialResponseWriter partialWriter =
          context.getPartialViewContext().getPartialResponseWriter();
      partialWriter.startUpdate(getStrategy(component).getContainerElementId(context, component));
      encodeItems(context, component, fetchValues);
      partialWriter.endUpdate();

      if (!fetchValues.isEmpty()) {
        Map<String, Object> dataMap =
            ExtendedPartialViewContext.getInstance(context).getResponseComponentDataMap();
        dataMap.put(component.getClientId(context), fetchValues);
      }
    } else {
      throw new IllegalArgumentException(metaComponentId);
    }
  }

  public void decodeMetaComponent(
      FacesContext context, UIComponent component, String metaComponentId) {
    throw new UnsupportedOperationException();
  }

  protected int getMinCharsOrDefault(UIComponent component) {
    int value = 1;
    if (component instanceof AbstractAutocomplete) {
      value = ((AbstractAutocomplete) component).getMinChars();
      if (value < 1) {
        value = 1;
      }
    }
    return value;
  }

  private Converter getConverterForValue(FacesContext context, UIComponent component) {
    Converter converter = ((ValueHolder) component).getConverter();
    if (converter == null) {
      ValueExpression expression = component.getValueExpression("value");

      if (expression != null) {
        Class<?> containerClass =
            ServiceTracker.getService(context, GenericsIntrospectionService.class)
                .getContainerClass(context, expression);

        converter = InputUtils.getConverterForType(context, containerClass);
      }
    }
    return converter;
  }

  @Override
  public Object getConvertedValue(FacesContext context, UIComponent component, Object val)
      throws ConverterException {
    String s = (String) val;
    Converter converter = getConverterForValue(context, component);
    if (converter != null) {
      return converter.getAsObject(context, component, s);
    } else {
      return s;
    }
  }
}
Ejemplo n.º 2
0
/**
 * The &lt;rich:togglePanel&gt; component is used as a base for the other switchable components, the
 * &lt;rich:accordion&gt; component and the &lt;rich:tabPanel&gt; component. It provides an abstract
 * switchable component without any associated markup. As such, the &lt;rich:togglePanel&gt;
 * component could be customized to provide a switchable component when neither an accordion
 * component or a tab panel component is appropriate.
 *
 * @author akolonitsky
 * @author <a href="http://community.jboss.org/people/bleathem">Brian Leathem</a>
 */
@JsfComponent(
    tag =
        @Tag(
            type = TagType.Facelets,
            handler = "org.richfaces.view.facelets.html.TogglePanelTagHandler"),
    renderer = @JsfRenderer(type = "org.richfaces.TogglePanelRenderer"),
    attributes = {"core-props.xml", "events-mouse-props.xml", "i18n-props.xml"})
public abstract class AbstractTogglePanel extends UIOutput
    implements AbstractDivPanel, ItemChangeSource, MetaComponentResolver, MetaComponentEncoder {
  public static final String ACTIVE_ITEM_META_COMPONENT = "activeItem";
  public static final String COMPONENT_TYPE = "org.richfaces.TogglePanel";
  public static final String COMPONENT_FAMILY = "org.richfaces.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 static final Logger LOG = RichfacesLogger.RENDERKIT.getLogger();
  private static final RendererUtils UTILS = RendererUtils.getInstance();

  private String submittedActiveItem = null;

  private enum PropertyKeys {
    localValueSet,
    required,
    valid,
    immediate,
    switchType
  }

  protected AbstractTogglePanel() {
    setRendererType("org.richfaces.TogglePanelRenderer");
  }

  public static boolean isPanelItemDynamic(UIComponent component) {
    UIComponent parent = component.getParent();
    while (parent != null) {
      if (parent instanceof AbstractTogglePanel) {
        return false;
      }
      if (parent instanceof UIRepeat) {
        return true;
      }
      parent = parent.getParent();
    }
    return false;
  }

  // -------------------------------------------------- Editable Value Holder

  public Object getSubmittedValue() {
    return this.submittedActiveItem;
  }

  public void resetValue() {
    this.setValue(null);
    this.setSubmittedValue(null);
    this.setLocalValueSet(false);
    this.setValid(true);
  }

  public void setSubmittedValue(Object submittedValue) {
    this.submittedActiveItem = String.valueOf(submittedValue);
  }

  public boolean isLocalValueSet() {
    return (Boolean) getStateHelper().eval(PropertyKeys.localValueSet, false);
  }

  public void setLocalValueSet(boolean localValueSet) {
    getStateHelper().put(PropertyKeys.localValueSet, 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);
  }

  /**
   * Set the "required field" state for this component.
   *
   * @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 facesContext) throws IOException {
    updateActiveName(getActiveItem());
    super.encodeBegin(facesContext);
  }

  public String updateActiveName(final String activeItemName) {
    boolean valid = false;

    if (!Strings.isNullOrEmpty(activeItemName)) {
      valid = isValidName(activeItemName);
    }

    if (!valid) {
      String firstItemName = getFirstNonDisabledItemName();
      if (firstItemName != null) {
        setActiveItem(firstItemName);
        return firstItemName;
      }
    }
    return activeItemName;
  }

  private Boolean isValidName(final String name) {
    final AtomicReference<Boolean> result = new AtomicReference<Boolean>(Boolean.FALSE);

    visitTogglePanelItems(
        this,
        new TogglePanelVisitCallback() {
          @Override
          public VisitResult visit(FacesContext facesContext, TogglePanelVisitState visitState) {
            AbstractTogglePanelItemInterface panelItem = visitState.getItem();
            if (name.equals(panelItem.getName())) {
              if (panelItem instanceof AbstractTogglePanelTitledItem) {
                AbstractTogglePanelTitledItem titledItem =
                    (AbstractTogglePanelTitledItem) panelItem;
                result.set(!titledItem.isDisabled());
              } else {
                result.set(Boolean.TRUE);
              }
              return VisitResult.COMPLETE;
            }
            return VisitResult.ACCEPT;
          }
        });

    return result.get();
  }

  /** Returns name of first non-disabled item in the list of panel's items. */
  private String getFirstNonDisabledItemName() {
    final AtomicReference<String> result = new AtomicReference<String>(null);

    visitTogglePanelItems(
        this,
        new TogglePanelVisitCallback() {
          @Override
          public VisitResult visit(FacesContext facesContext, TogglePanelVisitState visitState) {
            AbstractTogglePanelItemInterface panelItem = visitState.getItem();
            if (panelItem instanceof AbstractTogglePanelTitledItem) {
              AbstractTogglePanelTitledItem titledItem = (AbstractTogglePanelTitledItem) panelItem;
              if (!titledItem.isDisabled()) {
                result.set(titledItem.getName());
                return VisitResult.COMPLETE;
              } else {
                return VisitResult.ACCEPT;
              }
            } else {
              result.set(panelItem.getName());
              return VisitResult.COMPLETE;
            }
          }
        });

    return result.get();
  }

  /**
   * 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}.
   *
   * @throws NullPointerException {@inheritDoc}
   */
  @Override
  public void processDecodes(FacesContext facesContext) {
    if (facesContext == null) {
      throw new NullPointerException();
    }

    // Skip processing if our rendered flag is false
    if (!isRendered()) {
      return;
    }

    pushComponentToEL(facesContext, null);

    final String activeItem = getActiveItemValue();
    EnumSet<VisitHint> hints = EnumSet.of(VisitHint.SKIP_UNRENDERED);
    VisitContext visitContext = new FullVisitContext(facesContext, hints);
    this.visitTree(
        visitContext,
        new VisitCallback() {
          @Override
          public VisitResult visit(VisitContext context, UIComponent target) {
            if (AbstractTogglePanel.this == target || target instanceof UIRepeat) {
              return VisitResult.ACCEPT; // Proceed with visit to target's children
            }
            if (isActiveItem(target, activeItem) || getSwitchType() == SwitchType.client) {
              target.processDecodes(context.getFacesContext());
            }
            return VisitResult
                .REJECT; // No need to visit target's children, as they were recursively visited
                         // above
          }
        });

    // Process this component itself
    try {
      decode(facesContext);
    } catch (RuntimeException e) {
      facesContext.renderResponse();
      throw e;
    } finally {
      popComponentFromEL(facesContext);
    }

    ItemChangeEvent event = createItemChangeEvent(facesContext);
    if (event != null) {
      event.queue();
    }
  }

  /**
   * 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.
   *
   * @throws NullPointerException {@inheritDoc}
   */
  @Override
  public void processValidators(FacesContext facesContext) {
    if (facesContext == null) {
      throw new NullPointerException();
    }

    // Skip processing if our rendered flag is false
    if (!isRendered()) {
      return;
    }

    pushComponentToEL(facesContext, null);
    Application app = facesContext.getApplication();
    app.publishEvent(facesContext, PreValidateEvent.class, this);

    final String activeItem = getActiveItemValue();
    EnumSet<VisitHint> hints = EnumSet.of(VisitHint.SKIP_UNRENDERED);
    VisitContext visitContext = new FullVisitContext(facesContext, hints);
    this.visitTree(
        visitContext,
        new VisitCallback() {
          @Override
          public VisitResult visit(VisitContext context, UIComponent target) {
            if (AbstractTogglePanel.this == target || target instanceof UIRepeat) {
              return VisitResult.ACCEPT; // Proceed with visit to target's children
            }
            if (isActiveItem(target, activeItem) || getSwitchType() == SwitchType.client) {
              target.processValidators(context.getFacesContext());
            }
            return VisitResult
                .REJECT; // No need to visit target's children, as they were recursively visited
                         // above
          }
        });

    app.publishEvent(facesContext, PostValidateEvent.class, this);
    popComponentFromEL(facesContext);
  }

  /**
   * 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.
   *
   * @throws NullPointerException {@inheritDoc}
   */
  @Override
  public void processUpdates(FacesContext facesContext) {
    if (facesContext == null) {
      throw new NullPointerException();
    }

    // Skip processing if our rendered flag is false
    if (!isRendered()) {
      return;
    }

    pushComponentToEL(facesContext, null);

    final String activeItem = getActiveItemValue();
    EnumSet<VisitHint> hints = EnumSet.of(VisitHint.SKIP_UNRENDERED);
    VisitContext visitContext = new FullVisitContext(facesContext, hints);
    this.visitTree(
        visitContext,
        new VisitCallback() {
          @Override
          public VisitResult visit(VisitContext context, UIComponent target) {
            if (AbstractTogglePanel.this == target || target instanceof UIRepeat) {
              return VisitResult.ACCEPT; // Proceed with visit to target's children
            }
            if (isActiveItem(target, activeItem) || getSwitchType() == SwitchType.client) {
              target.processUpdates(context.getFacesContext());
            }
            return VisitResult
                .REJECT; // No need to visit target's children, as they were recursively visited
                         // above
          }
        });

    popComponentFromEL(facesContext);

    if (!isValid()) {
      facesContext.renderResponse();
    }
  }

  /** @throws NullPointerException {@inheritDoc} */
  @Override
  public void decode(FacesContext facesContext) {

    if (facesContext == null) {
      throw new NullPointerException();
    }

    // Force validity back to "true"
    setValid(true);
    super.decode(facesContext);
  }

  public void updateModel(FacesContext facesContext) {
    if (facesContext == null) {
      throw new NullPointerException();
    }

    if (!isValid() || !isLocalValueSet()) {
      return;
    }

    ValueExpression ve = getValueExpression("value");
    if (ve == null) {
      return;
    }

    Throwable caught = null;
    FacesMessage message = null;
    try {
      ve.setValue(facesContext.getELContext(), getLocalValue());
      setValue(null);
      setLocalValueSet(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(
                    facesContext,
                    FacesMessage.SEVERITY_ERROR,
                    FacesMessages.UIINPUT_UPDATE,
                    MessageUtil.getLabel(facesContext, this));
      } else {
        message = new FacesMessage(FacesMessage.SEVERITY_ERROR, messageStr, messageStr);
      }
      setValid(false);
    } catch (Exception e) {
      caught = e;
      // message = MessageFactory.getMessage(facesContext, UPDATE_MESSAGE_ID,
      // MessageFactory.getHeader(facesContext, this));
      setValid(false);
    }

    if (caught != null) {
      assert message != null;

      @SuppressWarnings({"ThrowableInstanceNeverThrown"})
      UpdateModelException toQueue = new UpdateModelException(message, caught);
      ExceptionQueuedEventContext eventContext =
          new ExceptionQueuedEventContext(facesContext, toQueue, this, PhaseId.UPDATE_MODEL_VALUES);
      facesContext
          .getApplication()
          .publishEvent(facesContext, ExceptionQueuedEvent.class, eventContext);
    }
  }

  private ItemChangeEvent createItemChangeEvent(FacesContext facesContext) {
    if (facesContext == 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 = (String) getValue();
    if (previous == null || !previous.equalsIgnoreCase(activeItem)) {
      UIComponent prevComp = null;
      UIComponent actvComp = null;

      if (previous != null) {
        try {
          prevComp = (UIComponent) getItem(previous);
        } catch (TogglePanelVisitException e) {
          if (LOG.isDebugEnabled()) {
            LOG.debug("Cannot include dynamic TogglePanelComponents in itemChangeEvents");
          }
          prevComp = null;
        }
      }
      if (activeItem != null) {
        try {
          if (LOG.isDebugEnabled()) {
            LOG.debug("Cannot include dynamic TogglePanelComponents in itemChangeEvents");
          }
          actvComp = (UIComponent) getItem(activeItem);
        } catch (TogglePanelVisitException e) {
          actvComp = null;
        }
      }

      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 facesContext = FacesContext.getCurrentInstance();
    if (event instanceof ItemChangeEvent) {
      setValue(((ItemChangeEvent) event).getNewItemName());
      setSubmittedActiveItem(null);
      if (event.getPhaseId() == PhaseId.UPDATE_MODEL_VALUES) {
        try {
          updateModel(facesContext);
        } catch (RuntimeException e) {
          facesContext.renderResponse();
          throw e;
        }
      } else {
        facesContext.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 || !(kid instanceof AbstractTogglePanelItemInterface)) {
      return false;
    }

    return isActiveItem((AbstractTogglePanelItemInterface) kid, value);
  }

  protected boolean isActiveItem(AbstractTogglePanelItemInterface item, String value) {
    if (item == null || value == null) {
      return false;
    }

    return item.getName().equals(value);
  }

  public TogglePanelVisitState visitTogglePanelItems(
      final AbstractTogglePanel panel, final TogglePanelVisitCallback callback) {
    FacesContext facesContext = getFacesContext();
    EnumSet<VisitHint> hints = EnumSet.of(VisitHint.SKIP_UNRENDERED);
    final TogglePanelVisitState visitState = new TogglePanelVisitState();

    VisitContext visitContext = new FullVisitContext(facesContext, hints);
    panel.visitTree(
        visitContext,
        new VisitCallback() {
          @Override
          public VisitResult visit(VisitContext context, UIComponent target) {
            if (target instanceof AbstractTogglePanelItemInterface) {
              AbstractTogglePanelItemInterface item = (AbstractTogglePanelItemInterface) target;
              visitState.setState(item.getName(), item);
              if (callback.visit(context.getFacesContext(), visitState) == VisitResult.COMPLETE) {
                visitState.setDynamic(item.isDynamicPanelItem());
                return VisitResult.COMPLETE;
              }
              visitState.increment();
              visitState.setState(null, null);
              return VisitResult.ACCEPT;
            } else if (AbstractTogglePanel.this == target || target instanceof UIRepeat) {
              return VisitResult.ACCEPT;
            } else {
              return VisitResult
                  .REJECT; // target is not a source of toggle panel items for this toggle panel
            }
          }
        });
    return visitState;
  }

  private TogglePanelVisitState getvisitStateByIndex(final int index) {
    TogglePanelVisitState visitState =
        visitTogglePanelItems(
            this,
            new TogglePanelVisitCallback() {
              @Override
              public VisitResult visit(
                  FacesContext facesContext, TogglePanelVisitState visitState) {
                if (index == visitState.getCount()) {
                  return VisitResult.COMPLETE;
                } else {
                  return VisitResult.ACCEPT;
                }
              }
            });
    return visitState;
  }

  public TogglePanelVisitState getVisitStateByName(final String name) {
    TogglePanelVisitState visitState =
        visitTogglePanelItems(
            this,
            new TogglePanelVisitCallback() {
              @Override
              public VisitResult visit(
                  FacesContext facesContext, TogglePanelVisitState visitState) {
                if (name.equals(visitState.getName())) {
                  return VisitResult.COMPLETE;
                } else {
                  return VisitResult.ACCEPT;
                }
              }
            });
    return visitState;
  }

  public AbstractTogglePanelItemInterface getItemByIndex(final int index) {
    TogglePanelVisitState visitState = getvisitStateByIndex(index);
    if (visitState.isDynamic()) {
      throw new TogglePanelVisitException(
          "Cannot access a dynamically generated AbstractToggleItemInterface directly. Use the visitor pattern instead.");
    }
    return visitState.getItem();
  }

  public String getNameByIndex(final int index) {
    if (!this.isRendered()) {
      return null;
    }
    return getvisitStateByIndex(index).getName();
  }

  public int getIndexByName(final String name) {
    if (!this.isRendered()) {
      return -1;
    }

    TogglePanelVisitState visitState = getVisitStateByName(name);
    if (visitState.getName() != null) {
      return visitState.getCount();
    } else {
      return -1;
    }
  }

  public String getClientIdByName(final String name) {
    if (!this.isRendered()) {
      return null;
    }

    TogglePanelVisitState visitState = getVisitStateByName(name);
    if (visitState.getName() != null) {
      return visitState.getClientId();
    } else {
      return null;
    }
  }

  public int getItemCount() {
    if (!this.isRendered()) {
      return 0;
    }
    TogglePanelVisitState visitState =
        visitTogglePanelItems(
            this,
            new TogglePanelVisitCallback() {
              @Override
              public VisitResult visit(
                  FacesContext facesContext, TogglePanelVisitState visitState) {
                return VisitResult.ACCEPT;
              }
            });
    return visitState.getCount();
  }

  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(getIndexByName(name) - 1);
  }

  public AbstractTogglePanelItemInterface getNextItem() {
    return getNextItem(getActiveItem());
  }

  public AbstractTogglePanelItemInterface getNextItem(String name) {
    return getItemByIndex(getIndexByName(name) + 1);
  }

  public AbstractTogglePanelItemInterface getLastItem() {
    return getItemByIndex(getItemCount());
  }

  @Deprecated
  public int getChildIndex(String name) {
    if (name == null) {
      throw new IllegalArgumentException("Name is required parameter.");
    }

    return getIndexByName(name);
  }

  // ------------------------------------------------

  public String getSubmittedActiveItem() {
    return submittedActiveItem;
  }

  public void setSubmittedActiveItem(String submittedActiveItem) {
    this.submittedActiveItem = submittedActiveItem;
  }

  // ------------------------------------------------ Properties

  @Override
  @Attribute(hidden = true)
  public void setValue(Object value) {
    super.setValue(value);

    setLocalValueSet(true);
  }

  /**
   * Holds the active panel name. This name is a reference to the name identifier of the active
   * child &lt;rich:togglePanelItem&gt; component.
   */
  @Attribute
  public String getActiveItem() {
    return (String) getValue();
  }

  public void setActiveItem(String value) {
    setValue(value);
  }

  @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 facesContext, String metaComponentId)
      throws IOException {
    ((MetaComponentRenderer) getRenderer(facesContext))
        .encodeMetaComponent(facesContext, 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) {
        Iterator<UIComponent> kids = this.getFacetsAndChildren();

        while (kids.hasNext()) {
          boolean done = kids.next().visitTree(context, callback);

          if (done) {
            return true;
          }
        }
      }
    } finally {
      popComponentFromEL(facesContext);
    }

    return false;
  }
}