001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.wicket.markup.html.link;
018:
019: import org.apache.wicket.Component;
020: import org.apache.wicket.IPageMap;
021: import org.apache.wicket.Page;
022: import org.apache.wicket.RequestCycle;
023: import org.apache.wicket.WicketRuntimeException;
024: import org.apache.wicket.markup.ComponentTag;
025: import org.apache.wicket.model.IModel;
026: import org.apache.wicket.util.string.Strings;
027: import org.apache.wicket.version.undo.Change;
028:
029: /**
030: * Implementation of a hyperlink component. A link can be used with an anchor
031: * (<a href...) element or any element that supports the onclick javascript
032: * event handler (such as buttons, td elements, etc). When used with an anchor,
033: * a href attribute will be generated. When used with any other element, an
034: * onclick javascript event handler attribute will be generated.
035: * <p>
036: * You can use a link like:
037: *
038: * <pre>
039: * add(new Link("myLink")
040: * {
041: * public void onClick(RequestCycle cycle)
042: * {
043: * // do something here...
044: * }
045: * );
046: * </pre>
047: *
048: * and in your HTML file:
049: *
050: * <pre>
051: * <a href="#" wicket:id="myLink">click here</a>
052: * </pre>
053: *
054: * or:
055: *
056: * <pre>
057: * <td wicket:id="myLink">my clickable column</td>
058: * </pre>
059: *
060: * </p>
061: * The following snippet shows how to pass a parameter from the Page creating
062: * the Page to the Page responded by the Link.
063: *
064: * <pre>
065: * add(new Link("link", listItem.getModel())
066: * {
067: * public void onClick()
068: * {
069: * MyObject obj = (MyObject)getModelObject();
070: * setResponsePage(new MyPage(obj.getId(), ... ));
071: * }
072: * </pre>
073: *
074: * @author Jonathan Locke
075: * @author Eelco Hillenius
076: */
077: public abstract class Link extends AbstractLink implements
078: ILinkListener {
079: /** Change record for when an anchor is changed. */
080: private final class AnchorChange extends Change {
081: private static final long serialVersionUID = 1L;
082:
083: /** the old anchor. */
084: private final Component anchor;
085:
086: /**
087: * Construct.
088: *
089: * @param anchor
090: */
091: public AnchorChange(Component anchor) {
092: this .anchor = anchor;
093: }
094:
095: public final void undo() {
096: Link.this .anchor = anchor;
097: }
098: }
099:
100: private static final long serialVersionUID = 1L;
101:
102: /**
103: * An anchor (form 'http://server/app/etc#someAnchor') will be appended to
104: * the link so that after this link executes, it will jump to the provided
105: * anchor component's position. The provided anchor must either have the
106: * {@link Component#getOutputMarkupId()} flag true, or it must be attached
107: * to a <a tag with a href attribute of more than one character starting
108: * with '#' ('<a href="#someAnchor" ... ').
109: */
110: private Component anchor;
111:
112: /**
113: * True if link should automatically enable/disable based on current page;
114: * false by default.
115: */
116: private boolean autoEnable = false;
117:
118: /**
119: * The popup specification. If not-null, a javascript on-click event handler
120: * will be generated that opens a new window using the popup properties.
121: */
122: private PopupSettings popupSettings = null;
123:
124: /**
125: * @see org.apache.wicket.Component#Component(String)
126: */
127: public Link(final String id) {
128: super (id);
129: }
130:
131: /**
132: * @see org.apache.wicket.Component#Component(String, IModel)
133: */
134: public Link(final String id, IModel object) {
135: super (id, object);
136: }
137:
138: /**
139: * Gets any anchor component.
140: *
141: * @return Any anchor component to jump to, might be null
142: */
143: public Component getAnchor() {
144: return anchor;
145: }
146:
147: /**
148: * Gets whether link should automatically enable/disable based on current
149: * page.
150: *
151: * @return Whether this link should automatically enable/disable based on
152: * current page.
153: */
154: public final boolean getAutoEnable() {
155: return autoEnable;
156: }
157:
158: /**
159: * Gets the popup specification. If not-null, a javascript on-click event
160: * handler will be generated that opens a new window using the popup
161: * properties.
162: *
163: * @return the popup specification.
164: */
165: public PopupSettings getPopupSettings() {
166: return popupSettings;
167: }
168:
169: /**
170: * @see org.apache.wicket.Component#isEnabled()
171: */
172: public boolean isEnabled() {
173: // If we're auto-enabling
174: if (getAutoEnable()) {
175: // the link is enabled if this link doesn't link to the current page
176: return !linksTo(getPage());
177: }
178: return super .isEnabled();
179: }
180:
181: protected boolean getStatelessHint() {
182: return false;
183: }
184:
185: /**
186: * Called when a link is clicked.
187: */
188: public abstract void onClick();
189:
190: /**
191: * THIS METHOD IS NOT PART OF THE WICKET API. DO NOT ATTEMPT TO OVERRIDE OR
192: * CALL IT.
193: *
194: * Called when a link is clicked. The implementation of this method is
195: * currently to simply call onClick(), but this may be augmented in the
196: * future.
197: *
198: * @see ILinkListener
199: */
200: public final void onLinkClicked() {
201: // if there are popupsettings and this link is clicked.
202: // set the popup page map in the request parameters, so that pages that
203: // are created in the onClick are made in the wanted pagemap
204: if (popupSettings != null) {
205: RequestCycle.get().getRequest().getRequestParameters()
206: .setPageMapName(
207: popupSettings.getPageMap(this ).getName());
208: }
209: // Invoke subclass handler
210: onClick();
211: }
212:
213: /**
214: * Sets an anchor component. An anchor (form
215: * 'http://server/app/etc#someAnchor') will be appended to the link so that
216: * after this link executes, it will jump to the provided anchor component's
217: * position. The provided anchor must either have the
218: * {@link Component#getOutputMarkupId()} flag true, or it must be attached
219: * to a <a tag with a href attribute of more than one character starting
220: * with '#' ('<a href="#someAnchor" ... ').
221: *
222: * @param anchor
223: * The anchor
224: * @return this
225: */
226: public Link setAnchor(Component anchor) {
227: addStateChange(new AnchorChange(this .anchor));
228: this .anchor = anchor;
229: return this ;
230: }
231:
232: /**
233: * Sets whether this link should automatically enable/disable based on
234: * current page.
235: *
236: * @param autoEnable
237: * whether this link should automatically enable/disable based on
238: * current page.
239: * @return This
240: */
241: public final Link setAutoEnable(final boolean autoEnable) {
242: this .autoEnable = autoEnable;
243: return this ;
244: }
245:
246: /**
247: * Sets the popup specification. If not-null, a javascript on-click event
248: * handler will be generated that opens a new window using the popup
249: * properties.
250: *
251: * @param popupSettings
252: * the popup specification.
253: * @return This
254: */
255: public final Link setPopupSettings(final PopupSettings popupSettings) {
256: this .popupSettings = popupSettings;
257: return this ;
258: }
259:
260: /**
261: * Appends any anchor to the url if the url is not null and the url does not
262: * already contain an anchor (url.indexOf('#') != -1). This implementation
263: * looks whether an anchor component was set, and if so, it will append the
264: * markup id of that component. That markup id is gotten by either calling
265: * {@link Component#getMarkupId()} if {@link Component#getOutputMarkupId()}
266: * returns true, or if the anchor component does not output it's id, this
267: * method will try to retrieve the id from the markup directly. If neither
268: * is found, an {@link WicketRuntimeException excpeption} is thrown. If no
269: * anchor component was set, but the link component is attached to a <a
270: * element, this method will append what is in the href attribute <i>if</i>
271: * there is one, starts with a '#' and has more than one character.
272: * <p>
273: * You can override this method, but it means that you have to take care of
274: * whatever is done with any set anchor component yourself. You also have to
275: * manually append the '#' at the right place.
276: * </p>
277: *
278: * @param tag
279: * The component tag
280: * @param url
281: * The url to start with
282: * @return The url, possibly with an anchor appended
283: */
284: protected CharSequence appendAnchor(final ComponentTag tag,
285: CharSequence url) {
286: if (url != null) {
287: Component anchor = getAnchor();
288: if (anchor != null) {
289: if (url.toString().indexOf('#') == -1) {
290: String id;
291: if (anchor.getOutputMarkupId()) {
292: id = anchor.getMarkupId();
293: } else {
294: id = anchor.getMarkupAttributes().getString(
295: "id");
296: }
297:
298: if (id != null) {
299: url = url + "#" + anchor.getMarkupId();
300: } else {
301: throw new WicketRuntimeException(
302: "an achor component was set on "
303: + this
304: + " but it neither has outputMarkupId set to true "
305: + "nor has a id set explicitly");
306: }
307: }
308: } else {
309: if (tag.getName().equalsIgnoreCase("a")) {
310: if (url.toString().indexOf('#') == -1) {
311: String href = tag.getAttributes().getString(
312: "href");
313: if (href != null && href.length() > 1
314: && href.charAt(0) == '#') {
315: url = url + href;
316: }
317: }
318: }
319: }
320: }
321: return url;
322: }
323:
324: /**
325: * @param url
326: * The url for the link
327: * @return Any onClick JavaScript that should be used
328: */
329: protected CharSequence getOnClickScript(final CharSequence url) {
330: return getOnClickScript(url.toString());
331: }
332:
333: /**
334: * @param url
335: * The url for the link
336: * @return Any onClick JavaScript that should be used
337: * @deprecated this method will be removed by
338: * {@link #getOnClickScript(CharSequence)} shortly. Please
339: * override that method instead.
340: */
341: protected String getOnClickScript(final String url) {
342: return null;
343: }
344:
345: /**
346: * Gets the url to use for this link.
347: *
348: * @return The URL that this link links to
349: */
350: protected CharSequence getURL() {
351: return urlFor(ILinkListener.INTERFACE);
352: }
353:
354: /**
355: * Whether this link refers to the given page.
356: *
357: * @param page
358: * A page
359: * @return True if this link goes to the given page
360: */
361: protected boolean linksTo(final Page page) {
362: return false;
363: }
364:
365: /**
366: * Handles this link's tag. OVERRIDES MUST CALL SUPER.
367: *
368: * @param tag
369: * the component tag
370: * @see org.apache.wicket.Component#onComponentTag(ComponentTag)
371: */
372: protected void onComponentTag(final ComponentTag tag) {
373: // Default handling for tag
374: super .onComponentTag(tag);
375:
376: // Set href to link to this link's linkClicked method
377: CharSequence url = getURL();
378:
379: // append any anchor
380: url = appendAnchor(tag, url);
381:
382: // If we're disabled
383: if (!isLinkEnabled()) {
384: disableLink(tag);
385: } else {
386: // if the tag is an anchor proper
387: if (tag.getName().equalsIgnoreCase("a")
388: || tag.getName().equalsIgnoreCase("link")
389: || tag.getName().equalsIgnoreCase("area")) {
390: // generate the href attribute
391: tag.put("href", Strings.replaceAll(url, "&", "&"));
392:
393: // Add any popup script
394: if (popupSettings != null) {
395: // NOTE: don't encode to HTML as that is not valid
396: // JavaScript
397: tag.put("onclick", popupSettings
398: .getPopupJavaScript());
399: }
400: } else if (tag.getName().equalsIgnoreCase("script")
401: || tag.getName().equalsIgnoreCase("style")) {
402: tag.put("src", Strings.replaceAll(url, "&", "&"));
403: } else {
404: // generate a popup script by asking popup settings for one
405: if (popupSettings != null) {
406: popupSettings.setTarget("'" + url + "'");
407: String popupScript = popupSettings
408: .getPopupJavaScript();
409: tag.put("onclick", popupScript);
410: } else {
411: // or generate an onclick JS handler directly
412: tag.put("onclick", "window.location.href='" + url
413: + "';return false;");
414: }
415: }
416:
417: // If the subclass specified javascript, use that
418: final CharSequence onClickJavaScript = getOnClickScript(url);
419: if (onClickJavaScript != null) {
420: tag.put("onclick", onClickJavaScript);
421: }
422:
423: if (popupSettings != null) {
424: IPageMap popupPageMap = popupSettings.getPageMap(this );
425: if (popupPageMap != null
426: && popupPageMap.getName() != null) {
427: tag.put("target", popupPageMap.getName());
428: }
429: }
430: }
431:
432: }
433: }
|