/*
* 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.link;
import org.apache.wicket.Component;
import org.apache.wicket.IPageMap;
import org.apache.wicket.Page;
import org.apache.wicket.RequestCycle;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.model.IModel;
import org.apache.wicket.util.string.Strings;
import org.apache.wicket.version.undo.Change;
/**
* Implementation of a hyperlink component. A link can be used with an anchor
* (<a href...) element or any element that supports the onclick javascript
* event handler (such as buttons, td elements, etc). When used with an anchor,
* a href attribute will be generated. When used with any other element, an
* onclick javascript event handler attribute will be generated.
* <p>
* You can use a link like:
*
* <pre>
* add(new Link("myLink")
* {
* public void onClick(RequestCycle cycle)
* {
* // do something here...
* }
* );
* </pre>
*
* and in your HTML file:
*
* <pre>
* <a href="#" wicket:id="myLink">click here</a>
* </pre>
*
* or:
*
* <pre>
* <td wicket:id="myLink">my clickable column</td>
* </pre>
*
* </p>
* The following snippet shows how to pass a parameter from the Page creating
* the Page to the Page responded by the Link.
*
* <pre>
* add(new Link("link", listItem.getModel())
* {
* public void onClick()
* {
* MyObject obj = (MyObject)getModelObject();
* setResponsePage(new MyPage(obj.getId(), ... ));
* }
* </pre>
*
* @author Jonathan Locke
* @author Eelco Hillenius
*/
public abstract class Link extends AbstractLink implements ILinkListener
{
/** Change record for when an anchor is changed. */
private final class AnchorChange extends Change
{
private static final long serialVersionUID = 1L;
/** the old anchor. */
private Component anchor;
/**
* Construct.
*
* @param anchor
*/
public AnchorChange(Component anchor)
{
this.anchor = anchor;
}
public final void undo()
{
Link.this.anchor = anchor;
}
}
private static final long serialVersionUID = 1L;
/**
* An anchor (form 'http://server/app/etc#someAnchor') will be appended to
* the link so that after this link executes, it will jump to the provided
* anchor component's position. The provided anchor must either have the
* {@link Component#getOutputMarkupId()} flag true, or it must be attached
* to a <a tag with a href attribute of more than one character starting
* with '#' ('<a href="#someAnchor" ... ').
*/
private Component anchor;
/**
* True if link should automatically enable/disable based on current page;
* false by default.
*/
private boolean autoEnable = false;
/**
* The popup specification. If not-null, a javascript on-click event handler
* will be generated that opens a new window using the popup properties.
*/
private PopupSettings popupSettings = null;
/**
* @see org.apache.wicket.Component#Component(String)
*/
public Link(final String id)
{
super(id);
}
/**
* @see org.apache.wicket.Component#Component(String, IModel)
*/
public Link(final String id, IModel object)
{
super(id, object);
}
/**
* Gets any anchor component.
*
* @return Any anchor component to jump to, might be null
*/
public Component getAnchor()
{
return anchor;
}
/**
* Gets whether link should automatically enable/disable based on current
* page.
*
* @return Whether this link should automatically enable/disable based on
* current page.
*/
public final boolean getAutoEnable()
{
return autoEnable;
}
/**
* Gets the popup specification. If not-null, a javascript on-click event
* handler will be generated that opens a new window using the popup
* properties.
*
* @return the popup specification.
*/
public PopupSettings getPopupSettings()
{
return popupSettings;
}
/**
* @see org.apache.wicket.Component#isEnabled()
*/
public boolean isEnabled()
{
// If we're auto-enabling
if (getAutoEnable())
{
// the link is enabled if this link doesn't link to the current page
return !linksTo(getPage());
}
return super.isEnabled();
}
protected boolean getStatelessHint()
{
return false;
}
/**
* Called when a link is clicked.
*/
public abstract void onClick();
/**
* THIS METHOD IS NOT PART OF THE WICKET API. DO NOT ATTEMPT TO OVERRIDE OR
* CALL IT.
*
* Called when a link is clicked. The implementation of this method is
* currently to simply call onClick(), but this may be augmented in the
* future.
*
* @see ILinkListener
*/
public final void onLinkClicked()
{
// if there are popupsettings and this link is clicked.
// set the popup page map in the request parameters, so that pages that
// are created in the onClick are made in the wanted pagemap
if (popupSettings != null)
{
RequestCycle.get().getRequest().getRequestParameters().setPageMapName(
popupSettings.getPageMap(this).getName());
}
// Invoke subclass handler
onClick();
}
/**
* Sets an anchor component. An anchor (form
* 'http://server/app/etc#someAnchor') will be appended to the link so that
* after this link executes, it will jump to the provided anchor component's
* position. The provided anchor must either have the
* {@link Component#getOutputMarkupId()} flag true, or it must be attached
* to a <a tag with a href attribute of more than one character starting
* with '#' ('<a href="#someAnchor" ... ').
*
* @param anchor
* The anchor
* @return this
*/
public Link setAnchor(Component anchor)
{
addStateChange(new AnchorChange(this.anchor));
this.anchor = anchor;
return this;
}
/**
* Sets whether this link should automatically enable/disable based on
* current page.
*
* @param autoEnable
* whether this link should automatically enable/disable based on
* current page.
* @return This
*/
public final Link setAutoEnable(final boolean autoEnable)
{
this.autoEnable = autoEnable;
return this;
}
/**
* Sets the popup specification. If not-null, a javascript on-click event
* handler will be generated that opens a new window using the popup
* properties.
*
* @param popupSettings
* the popup specification.
* @return This
*/
public final Link setPopupSettings(final PopupSettings popupSettings)
{
this.popupSettings = popupSettings;
return this;
}
/**
* Appends any anchor to the url if the url is not null and the url does not
* already contain an anchor (url.indexOf('#') != -1). This implementation
* looks whether an anchor component was set, and if so, it will append the
* markup id of that component. That markup id is gotten by either calling
* {@link Component#getMarkupId()} if {@link Component#getOutputMarkupId()}
* returns true, or if the anchor component does not output it's id, this
* method will try to retrieve the id from the markup directly. If neither
* is found, an {@link WicketRuntimeException excpeption} is thrown. If no
* anchor component was set, but the link component is attached to a <a
* element, this method will append what is in the href attribute <i>if</i>
* there is one, starts with a '#' and has more than one character.
* <p>
* You can override this method, but it means that you have to take care of
* whatever is done with any set anchor component yourself. You also have to
* manually append the '#' at the right place.
* </p>
*
* @param tag
* The component tag
* @param url
* The url to start with
* @return The url, possibly with an anchor appended
*/
protected CharSequence appendAnchor(final ComponentTag tag, CharSequence url)
{
if (url != null)
{
Component anchor = getAnchor();
if (anchor != null)
{
if (url.toString().indexOf('#') == -1)
{
String id;
if (anchor.getOutputMarkupId())
{
id = anchor.getMarkupId();
}
else
{
id = anchor.getMarkupAttributes().getString("id");
}
if (id != null)
{
url = url + "#" + anchor.getMarkupId();
}
else
{
throw new WicketRuntimeException("an achor component was set on " + this
+ " but it neither has outputMarkupId set to true "
+ "nor has a id set explicitly");
}
}
}
else
{
if (tag.getName().equalsIgnoreCase("a"))
{
if (url.toString().indexOf('#') == -1)
{
String href = tag.getAttributes().getString("href");
if (href != null && href.length() > 1 && href.charAt(0) == '#')
{
url = url + href;
}
}
}
}
}
return url;
}
/**
* @param url
* The url for the link
* @return Any onClick JavaScript that should be used
*/
protected CharSequence getOnClickScript(final CharSequence url)
{
return getOnClickScript(url.toString());
}
/**
* @param url
* The url for the link
* @return Any onClick JavaScript that should be used
* @deprecated this method will be removed by
* {@link #getOnClickScript(CharSequence)} shortly. Please
* override that method instead.
*/
protected String getOnClickScript(final String url)
{
return null;
}
/**
* Gets the url to use for this link.
*
* @return The URL that this link links to
*/
protected CharSequence getURL()
{
return urlFor(ILinkListener.INTERFACE);
}
/**
* Whether this link refers to the given page.
*
* @param page
* A page
* @return True if this link goes to the given page
*/
protected boolean linksTo(final Page page)
{
return false;
}
/**
* Handles this link's tag.
*
* @param tag
* the component tag
* @see org.apache.wicket.Component#onComponentTag(ComponentTag)
*/
protected void onComponentTag(final ComponentTag tag)
{
// Default handling for tag
super.onComponentTag(tag);
// Set href to link to this link's linkClicked method
CharSequence url = getURL();
// append any anchor
url = appendAnchor(tag, url);
// If we're disabled
if (!isLinkEnabled())
{
disableLink(tag);
}
else
{
// if the tag is an anchor proper
if (tag.getName().equalsIgnoreCase("a") || tag.getName().equalsIgnoreCase("link")
|| tag.getName().equalsIgnoreCase("area"))
{
// generate the href attribute
tag.put("href", Strings.replaceAll(url, "&", "&"));
// Add any popup script
if (popupSettings != null)
{
// NOTE: don't encode to HTML as that is not valid
// JavaScript
tag.put("onclick", popupSettings.getPopupJavaScript());
}
}
else if (tag.getName().equalsIgnoreCase("script")
|| tag.getName().equalsIgnoreCase("style"))
{
tag.put("src", Strings.replaceAll(url, "&", "&"));
}
else
{
// generate a popup script by asking popup settings for one
if (popupSettings != null)
{
popupSettings.setTarget("'" + url + "'");
String popupScript = popupSettings.getPopupJavaScript();
tag.put("onclick", popupScript);
}
else
{
// or generate an onclick JS handler directly
tag.put("onclick", "window.location.href='" + url + "';return false;");
}
}
// If the subclass specified javascript, use that
final CharSequence onClickJavaScript = getOnClickScript(url);
if (onClickJavaScript != null)
{
tag.put("onclick", onClickJavaScript);
}
if (popupSettings != null)
{
IPageMap popupPageMap = popupSettings.getPageMap(this);
if (popupPageMap != null && popupPageMap.getName() != null)
{
tag.put("target", popupPageMap.getName());
}
}
}
}
}