/*
* 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");
}
}
}