/* * 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.wicket.markup.html.internal; import java.util.Iterator; import org.apache.wicket.Component; import org.apache.wicket.MarkupContainer; import org.apache.wicket.WicketRuntimeException; import org.apache.wicket.markup.ComponentTag; import org.apache.wicket.markup.MarkupException; import org.apache.wicket.markup.MarkupStream; import org.apache.wicket.markup.html.TransparentWebMarkupContainer; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.EnclosureContainer; import org.apache.wicket.markup.parser.filter.EnclosureHandler; import org.apache.wicket.markup.resolver.ComponentResolvers; import org.apache.wicket.markup.resolver.ComponentResolvers.ResolverFilter; import org.apache.wicket.markup.resolver.IComponentResolver; import org.apache.wicket.util.string.Strings; /** * An Enclosure are automatically created by Wicket. Do not create it yourself. An Enclosure * container is created if <wicket:enclosure> is found in the markup. It is meant to solve the * following situation. Instead of * * <pre> * <table wicket:id="label-container" class="notify"><tr><td><span wicket:id="label">[[notification]]</span></td></tr></table> * WebMarkupContainer container=new WebMarkupContainer("label-container") * { * public boolean isVisible() * { * return hasNotification(); * } * }; * add(container); * container.add(new Label("label", notificationModel)); * </pre> * * with Enclosure you are able to do the following: * * <pre> * <wicket:enclosure> * <table class="notify"><tr><td><span wicket:id="label">[[notification]]</span></td></tr></table> * </wicket:enclosure> * add(new Label("label", notificationModel)) * { * public boolean isVisible() * { * return hasNotification(); * } * } * </pre> * <p> * Please note that since a transparent auto-component is created for the tag, the markup and the * component hierarchy will not be in sync which leads to subtle differences if your code relies on * onBeforeRender() and validate() being called for the children inside the enclosure tag. E.g. it * might happen that onBeforeRender() and validate() gets called on invisible components. In doubt, * please fall back to {@link EnclosureContainer}. * </p> * <p> * Additionally due to the reason above it is not possible to assert that children in Enclosure are * not visible to WicketTester. * </p> * * @see EnclosureHandler * @see EnclosureContainer * * @author igor * @author Juergen Donnerstag * @since 1.3 */ public class Enclosure extends WebMarkupContainer implements IComponentResolver { private static final long serialVersionUID = 1L; /** The child component to delegate the isVisible() call to */ private Component childComponent; /** Id of the child component that will control visibility of the enclosure */ private final String childId; /** * Construct. * * @param id * @param childId */ public Enclosure(final String id, final String childId) { super(id); if (childId == null) { throw new MarkupException( "You most likely forgot to register the EnclosureHandler with the MarkupParserFactory"); } this.childId = childId; } /** * * @return child id */ public final String getChildId() { return childId.toString(); } protected final Component getChild() { if (childComponent == null) { // try to find child when queued childComponent = resolveChild(this); } if (childComponent == null) { // try to find child when resolved childComponent = getChildComponent(new MarkupStream(getMarkup()), getEnclosureParent()); } return childComponent; } /** * Searches for the controlling child component looking also * through transparent components. * * @param container * the current container * @return the controlling child component, null if no one is found */ private Component resolveChild(MarkupContainer container) { Component childController = container.get(childId); Iterator<Component> children = container.iterator(); while (children.hasNext() && childController == null) { Component transparentChild = children.next(); if(transparentChild instanceof TransparentWebMarkupContainer) { childController = resolveChild((MarkupContainer)transparentChild); } } return childController; } @Override public boolean isVisible() { return getChild().determineVisibility(); } @Override protected void onConfigure() { super.onConfigure(); final Component child = getChild(); child.configure(); boolean childVisible = child.determineVisibility(); setVisible(childVisible); } @Override protected void onDetach() { super.onDetach(); // necessary when queued and lives with the page instead of just during render childComponent = null; } /** * Get the real parent container * * @return enclosure's parent markup container */ protected MarkupContainer getEnclosureParent() { MarkupContainer parent = getParent(); if (parent == null) { throw new WicketRuntimeException( "Unable to find parent component which is not a transparent resolver"); } return parent; } /** * Resolves the child component which is the controller of this Enclosure * * @param markupStream * the markup stream of this Enclosure * @param enclosureParent * the non-auto parent component of this Enclosure * @return The component associated with the {@linkplain #childId} */ private Component getChildComponent(final MarkupStream markupStream, MarkupContainer enclosureParent) { String fullChildId = getChildId(); Component controller = enclosureParent.get(fullChildId); if (controller == null) { int orgIndex = markupStream.getCurrentIndex(); try { while (markupStream.isCurrentIndexInsideTheStream()) { markupStream.next(); if (markupStream.skipUntil(ComponentTag.class)) { ComponentTag tag = markupStream.getTag(); if ((tag != null) && (tag.isOpen() || tag.isOpenClose())) { String tagId = tag.getId(); if (fullChildId.equals(tagId)) { ComponentTag fullComponentTag = new ComponentTag(tag); fullComponentTag.setId(childId.toString()); controller = ComponentResolvers.resolve(enclosureParent, markupStream, fullComponentTag, new ResolverFilter() { @Override public boolean ignoreResolver( final IComponentResolver resolver) { return resolver instanceof EnclosureHandler; } }); break; } else if (fullChildId.startsWith(tagId + PATH_SEPARATOR)) { fullChildId = Strings.afterFirst(fullChildId, PATH_SEPARATOR); } } } } } finally { markupStream.setCurrentIndex(orgIndex); } } checkChildComponent(controller); return controller; } @Override public Component resolve(MarkupContainer container, MarkupStream markupStream, ComponentTag tag) { if (childId.equals(tag.getId())) { return childComponent; } return getEnclosureParent().get(tag.getId()); } /** * * @param controller */ private void checkChildComponent(final Component controller) { if (controller == null) { throw new WicketRuntimeException("Could not find child with id: " + childId + " in the wicket:enclosure"); } else if (controller == this) { throw new WicketRuntimeException( "Programming error: childComponent == enclose component; endless loop"); } } }