blob: bc4708f20d03df9f131d8a5cf7a5e9abb5a9352d [file] [log] [blame]
/*
JSPWiki - a JSP-based WikiWiki clone.
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.wiki.tags;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.BodyContent;
import javax.servlet.jsp.tagext.BodyTagSupport;
import net.sourceforge.stripes.util.UrlBuilder;
import org.apache.wiki.WikiEngine;
import org.apache.wiki.i18n.InternationalizationManager;
import org.apache.wiki.tags.TabTag.Info;
/**
* <p>
* Generates a container for page tabs, as defined by collaborating
* {@link TabTag} tags. Works together with the tabbedSection JavaScript. The
* output of the two collaborating tags is two sets of <code>&lt;div&gt;</code>
* elements. The first one will have a class of <code>tabmenu</code>, and
* includes the tab names, accessibility keys and and URL that contains the
* tab's contents (if specified). The second <code>&lt;div&gt;</code>, of
* class <code>tabs</code>, contains additional nested
* <code>&lt;div&gt;</code> elements that contain the actual tab content, if
* any was enclosed by the <code>wiki:Tab</code> tags.
* </p>
* <p>
* For example, consider the following tags as defined on a JSP:
* </p>
* <blockquote><code>
* &lt;wiki:TabbedSection defaultTab="pagecontent"&gt;<br/>
* &nbsp;&nbsp;&lt;wiki:Tab id="pagecontent" title="View" accesskey="V"&gt;<br/>
* &nbsp;&nbsp;&nbsp;&nbsp;&lt;p&gt;This is the main tab.&lt;/p&gt;<br/>
* &nbsp;&nbsp;&lt;/wiki:Tab&gt;<br/>
* &nbsp;&nbsp;&lt;wiki:Tab id="info" title="Info" accesskey="I" url="/PageInfo.jsp?page=Main" /&gt;<br/>
* &lt;/wiki:TabbedSection&gt;
* </code></blockquote>
* <p>
* This will cause the following HTML to be generated when the page contents are
* returned to the browser:
* </p>
* <blockquote><code>
* &lt;div class="tabmenu"&gt;<br/>
* &nbsp;&nbsp;&lt;a class="activetab" id="menu-pagecontent" accesskey="v" &gt;&lt;span class='accesskey'&gt;V&lt;/span&gt;iew&lt;/a&gt;<br/>
* &nbsp;&nbsp;&lt;a id="menu-info" href='<var>web-context</var>/PageInfo.jsp?page=Main' accesskey="i" &gt;&lt;span class='accesskey'&gt;I&lt;/span&gt;nfo&lt;/a&gt;<br/>
* &lt;/div&gt;<br/>
* &lt;div class="tabs"&gt;<br/>
* &nbsp;&nbsp;&lt;div id="pagecontent"&gt;<br/>
* &nbsp;&nbsp;&nbsp;&nbsp;&lt;p&gt;This is the main tab.&lt;/p&gt;<br/>
* &nbsp;&nbsp;&lt;/div&gt;<br/>
* &nbsp;&nbsp;&lt;div id="info" class="hidetab" /&gt;<br/>
* &nbsp;&nbsp;&lt;div style="clear:both;" &gt;&lt;/div&gt;<br/>
* &lt;/div&gt;
* </code></blockquote>
* <h3>Attributes</h3>
* <ul>
* <li>defaultTab - Page name to refer to. Default is the first tab.</li>
* </ul>
*
* @since v2.3.63
*/
public class TabbedSectionTag extends BodyTagSupport
{
private static final long serialVersionUID = 2702437933960026481L;
private WikiEngine m_engine;
/**
* Returns the TabCollection for the current HttpServletRequest. This
* method is always guaranteed to return a valid TabCollection.
*
* @param request the servlet request
* @return the TabCollection
*/
public static TabCollection getTabContext( ServletRequest request )
{
TabCollection tc = (TabCollection) request.getAttribute( ATTR_TABS );
if( tc == null )
{
tc = new TabCollection();
request.setAttribute( ATTR_TABS, tc );
}
return tc;
}
private static final String ATTR_TABS = "JSPWiki.TabbedSection.Tags";
/**
* Holds the current set of related {@link TabbedSectionTag} and {@link TabTag}
* tags. One TabCollection is created for each HTTPServletRequest, rather than
* per PageContext, because the tags could span multiple pages.
*/
public static class TabCollection
{
/**
* Private constructor to prevent direct instantiation.
*/
private TabCollection()
{
super();
}
private final List<TabTag.Info> m_tabs = new ArrayList<TabTag.Info>();
/**
* Adds a child TabTag to the TabCollection. When the TabbedSection tag
* generates its menu and tab &lt;div&gt; elements, they will be
* generated in the order added. The tab added will be stored as a
* defensive copy, so that calls to
* {@link javax.servlet.jsp.tagext.Tag#release()} won't null out the
* cached copies.
*
* @param tab the tab to add
* @throws ClassNotFoundException
*/
public void addTab( TabTag tab ) throws JspTagException
{
if( tab == null )
{
throw new JspTagException( "Cannot add null TabTag." );
}
m_tabs.add( (Info)tab.getTabInfo().clone() );
}
/**
* Returns the list TabTag objects known to this TabCollection.
*
* @return the list of tab
*/
public List<TabTag.Info> getTabs()
{
return m_tabs;
}
/**
* Releases the TabCollection by clearing the internally cached list of
* TabTag objects. This method is called by
* {@link TabbedSectionTag#doEndTag()}.
*/
public void release()
{
m_tabs.clear();
}
}
/**
* {@inheritDoc}
*/
@Override
public void release()
{
super.release();
m_defaultTabID = null;
}
/**
* {@inheritDoc}
*/
@Override
public int doStartTag() throws JspTagException
{
m_engine = WikiEngine.getInstance( ((HttpServletRequest) pageContext.getRequest()).getSession().getServletContext(), null );
return EVAL_BODY_BUFFERED; /* always look inside */
}
/**
* The tabbed section iterates 3 time through the underlying Tab tags -
* first it identifies the default tab (displayed by default) - second it
* generates the tabmenu markup (displays all tab-titles) - finally it
* generates the content of each tab.
*
* @return {@inheritDoc}
* @throws {@inheritDoc}
*/
@Override
public int doAfterBody() throws JspTagException
{
// Stash the tag body (previously evaluated)
BodyContent body = getBodyContent();
String bodyString = body.getString();
// Figure out the active (default) tab
TabCollection tc = getTabContext( pageContext.getRequest() );
List<TabTag.Info> tabs = tc.getTabs();
try
{
// Generate menu divs; output to enclosing writer
body.clear();
JspWriter writer = this.getPreviousOut();
writer.append( "<div class=\"tabmenu\">\n" );
for( TabTag.Info tab : tabs )
{
// Is this the default tab?
String id = tab.options.get( Info.ID );
if( id.equals( m_defaultTabID ) )
{
m_defaultTabID = id;
}
// If default tag still not set, use the first one
if( m_defaultTabID == null || m_defaultTabID.length() == 0 )
{
m_defaultTabID = id;
}
// Generate each menu item div
writeTabMenuItem( writer, tab );
}
writer.append( "</div>\n" );
// Output the opening "tabs" div
writer.append( "<div class=\"tabs\">" );
// Remove the "hidden" class from the active tab
String activeTabDiv = "<div id=\"" + m_defaultTabID + "\" class=\"hidetab\">";
bodyString = bodyString.replace( activeTabDiv, "<div id=\"" + m_defaultTabID + "\">" );
// Write back the stashed tag body
writer.append( bodyString );
// Append our closing div tags
writer.append( " <div style=\"clear:both;\" ></div>\n</div>\n" );
}
catch( IOException e )
{
throw new JspTagException( e );
}
return SKIP_BODY;
}
public int doEndTag() throws JspException
{
// Clear the TabCollection for the next caller
TabCollection tc = getTabContext( pageContext.getRequest() );
tc.release();
return super.doEndTag();
}
/**
* Outputs a single menu item <code>div</code> element for a supplied tag.
*
* @param writer the JspWriter to write the output to
* @param tab the TabTag.Info object containing information about the tab
* @throws IOException
*/
private void writeTabMenuItem( JspWriter writer, TabTag.Info tab ) throws IOException
{
writer.append( " <a" );
// Generate the ID
String id = tab.options.get( Info.ID );
writer.append( " id=\"menu-" + id + "\"" );
// Active tab?
if( id.equals( m_defaultTabID ) )
{
writer.append( " class=\"activetab\"" );
}
// Onclick event?
if ( tab.options.get( Info.ON_CLICK ) != null )
{
writer.append( " onclick=\""+ tab.options.get( Info.ON_CLICK ) + "\"" );
}
// Generate the ActionBean event URL, if supplied
if ( tab.beanclass != null )
{
HttpServletRequest request = (HttpServletRequest)pageContext.getRequest();
UrlBuilder builder = new UrlBuilder( request.getLocale(), tab.beanclass, true );
if ( tab.options.get( Info.EVENT ) != null )
{
builder.setEvent( tab.options.get( Info.EVENT ) );
}
for ( Map.Entry<String, String> entry : tab.containedParams.entrySet() )
{
builder.addParameter( entry.getKey(), entry.getValue() );
}
String url = builder.toString();
if ( request.getContextPath() != null && !url.startsWith( request.getContextPath() ) )
{
url = request.getContextPath() + url;
}
writer.append( " href='" + url + "'" );
}
// Generate the URL, if supplied
else if( tab.options.get( Info.URL ) != null )
{
writer.append( " href='" + tab.options.get( Info.URL ) + "'" );
}
// Generate the tab title
String tabTitle = null;
if( tab.options.get( Info.TITLE_KEY ) != null )
{
Locale locale = pageContext.getRequest().getLocale();
InternationalizationManager i18n = m_engine.getInternationalizationManager();
tabTitle = i18n.get( InternationalizationManager.TEMPLATES_BUNDLE, locale, tab.options.get( Info.TITLE_KEY ) );
}
if( tabTitle == null )
{
tabTitle = tab.options.get( Info.TITLE );
}
writer.append( ">" );
// Output the tab title
String accesskey = tab.options.get( Info.ACCESS_KEY );
if( tabTitle != null )
{
// Generate the access key, if supplied
if( accesskey != null )
{
int pos = tabTitle.toLowerCase().indexOf( accesskey.toLowerCase() );
if( pos > -1 )
{
tabTitle = tabTitle.substring( 0, pos ) + "<span class='accesskey'>" + tabTitle.charAt( pos ) + "</span>"
+ tabTitle.substring( pos + 1 );
}
}
writer.append( tabTitle );
}
// Output the closing tag
writer.append( "</a>\n" );
}
private String m_defaultTabID = null;
/**
* Returns the default tab ID.
*
* @return the tab ID
*/
protected String getDefaultTab()
{
return m_defaultTabID;
}
/**
* Sets the id of the default tab. If not set, the first {@link TabTag}
* element encountered will be used as the default.
*
* @param defaultTab the id of tab to use as the default
*/
public void setDefaultTab( String defaultTab )
{
m_defaultTabID = defaultTab;
}
}