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