/*
* 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.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.wicket.Component;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.Response;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.MarkupStream;
import org.apache.wicket.markup.html.IHeaderResponse;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.response.StringResponse;
/**
* The HtmlHeaderContainer is automatically created and added to the component
* hierarchy by a HtmlHeaderResolver instance. HtmlHeaderContainer tries to
* handle/render the >head> tag and its body. However depending on the
* parent component, the behavior must be different. E.g. if parent component is
* a Page all components of the page's hierarchy must be asked if they have
* something to contribute to the <head> section of the html response. If
* yes, it must <b>immediately</b> be rendered.
* <p>
* <head> regions may contain additional wicket components, which can be
* added by means of add(Component) as usual.
* <p>
* <wicket:head> tags are handled by simple WebMarkupContainers also
* created by a HtmlHeaderResolver.
* <p>
* <ul>
* <li> <head> will be inserted in output automatically if required</li>
* <li> <head> is <b>not</b> a wicket specific tag and you must use add()
* to add components referenced in body of the head tag</li>
* <li> <head> is supported by panels, borders and inherited markup, but
* is <b>not</b> copied to the output. They are for previewability only (except
* on Pages)</li>
* <li> <wicket:head> does not make sense in page markup (but does in
* inherited page markup)</li>
* <li> <wicket:head> makes sense in Panels, Borders and inherited markup
* (of Panels, Borders and Pages)</li>
* <li> components within <wicket:head> must be added by means of add(),
* like allways with Wicket. No difference.</li>
* <li> <wicket:head> and it's content is copied to the output. Components
* contained in <org.apache.wicket.head> are rendered as usual</li>
* </ul>
*
* @author Juergen Donnerstag
*/
public class HtmlHeaderContainer extends WebMarkupContainer
{
private static final long serialVersionUID = 1L;
/**
* wicket:head tags (components) must only be added once. To allow for a
* little bit more control, each wicket:head has an associated scope which
* by default is equal to the java class name directly associated with the
* markup which contains the wicket:head. It can be modified by means of the
* scope attribute.
*/
private transient Map renderedComponentsPerScope;
/**
* Header response that is responsible for filtering duplicate
* contributions.
*/
private transient IHeaderResponse headerResponse = null;
/**
* Construct
*
* @see Component#Component(String)
*/
public HtmlHeaderContainer(final String id)
{
super(id);
// We will render the tags manually, because if no component asked to
// contribute to the header, the tags will not be printed either.
// No contribution usually only happens if none of the components
// including the page does have a <head> or <wicket:head> tag.
setRenderBodyOnly(true);
setAuto(true);
}
/**
* First render the body of the component. And if it is the header component
* of a Page (compared to a Panel or Border), than get the header sections
* from all component in the hierachie and render them as well.
*
* @see org.apache.wicket.MarkupContainer#onComponentTagBody(org.apache.wicket.markup.MarkupStream,
* org.apache.wicket.markup.ComponentTag)
*/
protected final void onComponentTagBody(MarkupStream markupStream, ComponentTag openTag)
{
// We are able to automatically add <head> to the page if it is
// missing. But we only want to add it, if we have content to be
// written to its body. Thus we first write the output into a
// StringResponse and if not empty, we copy it to the original
// web response.
// Temporarily replace the web response with a String response
final Response webResponse = this.getResponse();
try
{
final StringResponse response = new StringResponse();
this.getRequestCycle().setResponse(response);
// In any case, first render the header section directly associated
// with the markup
super.onComponentTagBody(markupStream, openTag);
// Render all header sections of all components on the page
renderHeaderSections(getPage(), this);
// Automatically add <head> if necessary
CharSequence output = response.getBuffer();
if (output.length() > 0)
{
if (output.charAt(0) == '\r')
{
for (int i = 2; i < output.length(); i += 2)
{
char ch = output.charAt(i);
if (ch != '\r')
{
output = output.subSequence(i - 2, output.length());
break;
}
}
}
else if (output.charAt(0) == '\n')
{
for (int i = 1; i < output.length(); i++)
{
char ch = output.charAt(i);
if (ch != '\n')
{
output = output.subSequence(i - 1, output.length());
break;
}
}
}
}
if (output.length() > 0)
{
webResponse.write("<head>");
webResponse.write(output);
webResponse.write("</head>");
}
}
finally
{
// Restore the original response
this.getRequestCycle().setResponse(webResponse);
}
}
/**
* Ask all child components of the Page if they have something to contribute
* to the <head> section of the HTML output. Every component
* interested must implement IHeaderContributor.
* <p>
* Note: HtmlHeaderContainer will be removed from the component hierachie at
* the end of the request (@see #onEndRequest()) and thus can not transport
* status from one request to the next. This is true for all components
* added to the header.
*
* @param page
* Usually it is the page object, but there might also be that a WebMarkupContainer has been attached to the <html> tag
* @param container
* The header component container
*/
private final void renderHeaderSections(final MarkupContainer page, final HtmlHeaderContainer container)
{
page.renderHead(container);
// Make sure all Components interested in contributing to the header
// and there attached behaviors are asked.
page.visitChildren(new IVisitor()
{
/**
* @see org.apache.wicket.Component.IVisitor#component(org.apache.wicket.Component)
*/
public Object component(Component component)
{
if (component.isVisible())
{
component.renderHead(container);
return IVisitor.CONTINUE_TRAVERSAL;
}
else
{
return IVisitor.CONTINUE_TRAVERSAL_BUT_DONT_GO_DEEPER;
}
}
});
}
/**
* @see org.apache.wicket.MarkupContainer#isTransparentResolver()
*/
public boolean isTransparentResolver()
{
return true;
}
/**
* Check if the header component is ok to render within the scope given.
*
* @param scope
* The scope of the header component
* @param id
* The component's id
* @return true, if the component ok to render
*/
public final boolean okToRenderComponent(final String scope, final String id)
{
if (this.renderedComponentsPerScope == null)
{
this.renderedComponentsPerScope = new HashMap();
}
// if (scope == null)
// {
// scope = header.getMarkupStream().getContainerClass().getName();
// }
List componentScope = (List)this.renderedComponentsPerScope.get(scope);
if (componentScope == null)
{
componentScope = new ArrayList();
this.renderedComponentsPerScope.put(scope, componentScope);
}
if (componentScope.contains(id))
{
return false;
}
componentScope.add(id);
return true;
}
protected void onDetach()
{
super.onDetach();
this.renderedComponentsPerScope = null;
this.headerResponse = null;
}
/**
* Factory method for creating header response
*
* @param response
* @return new header response
*/
protected IHeaderResponse newHeaderResponse()
{
return new HeaderResponse() {
public Response getResponse()
{
return HtmlHeaderContainer.this.getResponse();
}
};
}
/**
* Returns the header response.
*
* @return header response
*/
public IHeaderResponse getHeaderResponse()
{
if (this.headerResponse == null)
{
headerResponse = newHeaderResponse();
}
return headerResponse;
}
}