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 */ 017package org.apache.wicket.markup.html.link; 018 019import org.apache.wicket.Component; 020import org.apache.wicket.IGenericComponent; 021import org.apache.wicket.IRequestListener; 022import org.apache.wicket.Page; 023import org.apache.wicket.WicketRuntimeException; 024import org.apache.wicket.markup.ComponentTag; 025import org.apache.wicket.markup.head.IHeaderResponse; 026import org.apache.wicket.markup.head.OnEventHeaderItem; 027import org.apache.wicket.model.IModel; 028import org.apache.wicket.request.mapper.parameter.PageParameters; 029 030/** 031 * Implementation of a hyperlink component. A link can be used with an anchor (<a href...) 032 * element or any element that supports the onclick javascript event handler (such as buttons, td 033 * elements, etc). When used with an anchor, a href attribute will be generated. When used with any 034 * other element, a click javascript event handler will be added. 035 * <p> 036 * You can use a link like: 037 * 038 * <pre> 039 * add(new Link("myLink") 040 * { 041 * public void onClick() 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 the Page to the Page 062 * responded by the Link. 063 * 064 * <pre> 065 * add(new Link<MyObject>("link", listItem.getModel()) 066 * { 067 * public void onClick() 068 * { 069 * MyObject obj = getModelObject(); 070 * setResponsePage(new MyPage(obj)); 071 * } 072 * </pre> 073 * 074 * @author Jonathan Locke 075 * @author Eelco Hillenius 076 * @param <T> 077 * type of model object 078 */ 079public abstract class Link<T> extends AbstractLink implements IRequestListener, IGenericComponent<T, Link<T>> 080{ 081 private static final long serialVersionUID = 1L; 082 083 /** 084 * An anchor (form 'http://server/app/etc#someAnchor') will be appended to the link so that 085 * after this link executes, it will jump to the provided anchor component's position. The 086 * provided anchor must either have the {@link Component#getOutputMarkupId()} flag true, or it 087 * must be attached to a <a tag with a href attribute of more than one character starting 088 * with '#' ('<a href="#someAnchor" ... '). 089 */ 090 private Component anchor; 091 092 /** 093 * True if link should automatically enable/disable based on current page; false by default. 094 */ 095 private boolean autoEnable = false; 096 097 /** 098 * The popup specification. If not-null, a javascript on-click event handler will be generated 099 * that opens a new window using the popup properties. 100 */ 101 private PopupSettings popupSettings = null; 102 103 /** 104 * @see org.apache.wicket.Component#Component(String) 105 */ 106 public Link(final String id) 107 { 108 super(id); 109 } 110 111 /** 112 * @param id 113 * @param model 114 * @see org.apache.wicket.Component#Component(String, IModel) 115 */ 116 public Link(final String id, IModel<T> model) 117 { 118 super(id, model); 119 } 120 121 /** 122 * Gets any anchor component. 123 * 124 * @return Any anchor component to jump to, might be null 125 */ 126 public Component getAnchor() 127 { 128 return anchor; 129 } 130 131 /** 132 * Gets whether link should automatically enable/disable based on current page. 133 * 134 * @return Whether this link should automatically enable/disable based on current page. 135 */ 136 public final boolean getAutoEnable() 137 { 138 return autoEnable; 139 } 140 141 /** 142 * Gets the popup specification. If not-null, a javascript on-click event handler will be 143 * generated that opens a new window using the popup properties. 144 * 145 * @return the popup specification. 146 */ 147 public final PopupSettings getPopupSettings() 148 { 149 return popupSettings; 150 } 151 152 /** 153 * @see org.apache.wicket.Component#isEnabled() 154 */ 155 @Override 156 public boolean isEnabled() 157 { 158 // If we're auto-enabling 159 if (getAutoEnable()) 160 { 161 // the link is enabled if this link doesn't link to the current page 162 return !linksTo(getPage()); 163 } 164 return super.isEnabled(); 165 } 166 167 /** 168 * @see org.apache.wicket.Component#getStatelessHint() 169 */ 170 @Override 171 protected boolean getStatelessHint() 172 { 173 return false; 174 } 175 176 /** 177 * Called when a link is clicked. 178 */ 179 public abstract void onClick(); 180 181 /** 182 * THIS METHOD IS NOT PART OF THE WICKET API. DO NOT ATTEMPT TO OVERRIDE OR CALL IT. 183 * 184 * Called when a link is clicked. The implementation of this method is currently to simply call 185 * onClick(), but this may be augmented in the future. 186 */ 187 @Override 188 public void onRequest() 189 { 190 // Invoke subclass handler 191 onClick(); 192 } 193 194 /** 195 * Sets an anchor component. An anchor (form 'http://server/app/etc#someAnchor') will be 196 * appended to the link so that after this link executes, it will jump to the provided anchor 197 * component's position. The provided anchor must either have the 198 * {@link Component#getOutputMarkupId()} flag true, or it must be attached to a <a tag with a 199 * href attribute of more than one character starting with '#' ('<a href="#someAnchor" ... 200 * '). 201 * 202 * @param anchor 203 * The anchor 204 * @return this 205 */ 206 public Link<T> setAnchor(Component anchor) 207 { 208 addStateChange(); 209 this.anchor = anchor; 210 return this; 211 } 212 213 /** 214 * Sets whether this link should automatically enable/disable based on current page. 215 * 216 * @param autoEnable 217 * whether this link should automatically enable/disable based on current page. 218 * @return This 219 */ 220 public final Link<T> setAutoEnable(final boolean autoEnable) 221 { 222 this.autoEnable = autoEnable; 223 return this; 224 } 225 226 /** 227 * Sets the popup specification. If not-null, a javascript on-click event handler will be 228 * generated that opens a new window using the popup properties. 229 * 230 * @param popupSettings 231 * the popup specification. 232 * @return This 233 */ 234 public final Link<T> setPopupSettings(final PopupSettings popupSettings) 235 { 236 this.popupSettings = popupSettings; 237 return this; 238 } 239 240 /** 241 * Appends any anchor to the url if the url is not null and the url does not already contain an 242 * anchor (url.indexOf('#') != -1). This implementation looks whether an anchor component was 243 * set, and if so, it will append the markup id of that component. That markup id is gotten by 244 * either calling {@link Component#getMarkupId()} if {@link Component#getOutputMarkupId()} 245 * returns true, or if the anchor component does not output it's id, this method will try to 246 * retrieve the id from the markup directly. If neither is found, an 247 * {@link WicketRuntimeException exception} is thrown. If no anchor component was set, but the 248 * link component is attached to a <a element, this method will append what is in the href 249 * attribute <i>if</i> there is one, starts with a '#' and has more than one character. 250 * <p> 251 * You can override this method, but it means that you have to take care of whatever is done 252 * with any set anchor component yourself. You also have to manually append the '#' at the right 253 * place. 254 * </p> 255 * 256 * @param tag 257 * The component tag 258 * @param url 259 * The url to start with 260 * @return The url, possibly with an anchor appended 261 */ 262 protected CharSequence appendAnchor(final ComponentTag tag, CharSequence url) 263 { 264 if (url != null) 265 { 266 Component anchor = getAnchor(); 267 if (anchor != null) 268 { 269 if (url.toString().indexOf('#') == -1) 270 { 271 String id; 272 if (anchor.getOutputMarkupId()) 273 { 274 id = anchor.getMarkupId(); 275 } 276 else 277 { 278 id = anchor.getMarkupAttributes().getString("id"); 279 } 280 281 if (id != null) 282 { 283 url = url + "#" + anchor.getMarkupId(); 284 } 285 else 286 { 287 throw new WicketRuntimeException("an achor component was set on " + this + 288 " but it neither has outputMarkupId set to true " + 289 "nor has a id set explicitly"); 290 } 291 } 292 } 293 else 294 { 295 if (tag.getName().equalsIgnoreCase("a")) 296 { 297 if (url.toString().indexOf('#') == -1) 298 { 299 String href = tag.getAttributes().getString("href"); 300 if (href != null && href.length() > 1 && href.charAt(0) == '#') 301 { 302 url = url + href; 303 } 304 } 305 } 306 } 307 } 308 return url; 309 } 310 311 /** 312 * @param url 313 * The url for the link 314 * @return Any onClick JavaScript that should be used 315 */ 316 protected CharSequence getOnClickScript(final CharSequence url) 317 { 318 return null; 319 } 320 321 /** 322 * Gets the url to use for this link. 323 * 324 * @return The URL that this link links to 325 */ 326 protected CharSequence getURL() 327 { 328 return urlForListener(new PageParameters()); 329 } 330 331 /** 332 * Whether this link refers to the given page. 333 * 334 * @param page 335 * A page 336 * @return True if this link goes to the given page 337 */ 338 protected boolean linksTo(final Page page) 339 { 340 return false; 341 } 342 343 /** 344 * Handles this link's tag. OVERRIDES MUST CALL SUPER. 345 * 346 * @param tag 347 * the component tag 348 * @see org.apache.wicket.Component#onComponentTag(ComponentTag) 349 */ 350 @Override 351 protected void onComponentTag(final ComponentTag tag) 352 { 353 // Default handling for tag 354 super.onComponentTag(tag); 355 356 // If we're disabled 357 if (isEnabledInHierarchy()) 358 { 359 // Set href to link to this link's linkClicked method 360 CharSequence url = getURL(); 361 362 // append any anchor 363 url = appendAnchor(tag, url); 364 365 // if the tag is an anchor proper 366 if (tag.getName().equalsIgnoreCase("a") || tag.getName().equalsIgnoreCase("link") || 367 tag.getName().equalsIgnoreCase("area")) 368 { 369 // generate the href attribute 370 tag.put("href", url); 371 } 372 else if (tag.getName().equalsIgnoreCase("script") || 373 tag.getName().equalsIgnoreCase("style")) 374 { 375 tag.put("src", url); 376 } 377 } 378 else 379 { 380 disableLink(tag); 381 } 382 } 383 384 @Override 385 public void renderHead(IHeaderResponse response) 386 { 387 super.renderHead(response); 388 // If we're disabled 389 if (isEnabledInHierarchy() && useJSEventBindingWhenNeeded()) 390 { 391 ComponentTag tag = getMarkupTag(); 392 393 // Set href to link to this link's linkClicked method 394 CharSequence url = getURL(); 395 396 // append any anchor 397 url = appendAnchor(tag, url); 398 399 // If the subclass specified javascript, use that 400 final CharSequence onClickJavaScript = getOnClickScript(url); 401 if (onClickJavaScript != null) 402 { 403 response.render(OnEventHeaderItem.forComponent(this, "click", onClickJavaScript)); 404 return; 405 } 406 407 // next check for popup settings 408 if (popupSettings != null) 409 { 410 popupSettings.setTarget("'" + url + "'"); 411 response.render(OnEventHeaderItem.forComponent(this, "click", 412 popupSettings.getPopupJavaScript())); 413 return; 414 } 415 416 // finally, when the tag is not a normal link 417 if (!(tag.getName().equalsIgnoreCase("a") || tag.getName().equalsIgnoreCase("link") 418 || tag.getName().equalsIgnoreCase("area") 419 || tag.getName().equalsIgnoreCase("script") 420 || tag.getName().equalsIgnoreCase("style"))) 421 { 422 // generate an onclick JS handler directly 423 // in firefox when the element is quickly clicked 3 times a second request is 424 // generated during page load. This check ensures that the click is ignored 425 response.render(OnEventHeaderItem.forComponent(this, "click", 426 "var win = this.ownerDocument.defaultView || this.ownerDocument.parentWindow; " 427 + "if (win == window) { window.location.href='" + url 428 + "'; } ;return false")); 429 return; 430 } 431 } 432 } 433 434 /** 435 * This method can be overridden by a subclass to disable the JS event binding or provide custom 436 * event binding code is used. 437 * 438 * @return true when a javascripot event binding must used to handle the click event. 439 */ 440 protected boolean useJSEventBindingWhenNeeded() 441 { 442 return true; 443 } 444}