001// Copyright 2007-2013The Apache Software Foundation 002// 003// Licensed under the Apache License, Version 2.0 (the "License"); 004// you may not use this file except in compliance with the License. 005// You may obtain a copy of the License at 006// 007// http://www.apache.org/licenses/LICENSE-2.0 008// 009// Unless required by applicable law or agreed to in writing, software 010// distributed under the License is distributed on an "AS IS" BASIS, 011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012// See the License for the specific language governing permissions and 013// limitations under the License. 014 015package org.apache.tapestry5.corelib.base; 016 017import org.apache.tapestry5.*; 018import org.apache.tapestry5.annotations.Parameter; 019import org.apache.tapestry5.annotations.SetupRender; 020import org.apache.tapestry5.annotations.SupportsInformalParameters; 021import org.apache.tapestry5.dom.Element; 022import org.apache.tapestry5.ioc.annotations.Inject; 023import org.apache.tapestry5.services.javascript.JavaScriptSupport; 024 025import java.util.Map; 026 027/** 028 * Provides base utilities for classes that generate clickable links. 029 */ 030@SupportsInformalParameters 031public abstract class AbstractLink implements ClientElement 032{ 033 /** 034 * An anchor value to append to the generated URL (the hash separator will be added automatically). 035 */ 036 @Parameter(defaultPrefix = BindingConstants.LITERAL) 037 private String anchor; 038 039 /** 040 * If true, then then no link element is rendered (and no informal parameters as well). The body is, however, still 041 * rendered. 042 */ 043 @Parameter("false") 044 private boolean disabled; 045 046 /** 047 * If specified, the parameters are added to the link as query parameters in key=value fashion. 048 * Values will be coerced to string using value encoder; keys should be Strings. 049 * @since 5.3 050 */ 051 @Parameter(allowNull = false) 052 private Map<String, ?> parameters; 053 054 @Inject 055 protected ComponentResources resources; 056 057 @Inject 058 private JavaScriptSupport jsSupport; 059 060 private Link link; 061 062 private Element element; 063 064 private String clientId; 065 066 private String buildHref(Link link) 067 { 068 String href = link.toURI(); 069 070 if (anchor == null) 071 return href; 072 073 return href + "#" + anchor; 074 } 075 076 @SetupRender 077 void resetElementAndClientId() 078 { 079 element = null; 080 clientId = null; 081 } 082 083 /** 084 * Writes an <a> element with the provided link as the href attribute. A call to 085 * {@link org.apache.tapestry5.MarkupWriter#end()} is <em>not</em> provided. Automatically appends an anchor if 086 * the component's anchor parameter is non-null. Informal parameters are rendered as well. 087 * 088 * @param writer 089 * to write markup to 090 * @param link 091 * the link that will form the href 092 * @param namesAndValues 093 * additional attributes to write 094 */ 095 protected final void writeLink(MarkupWriter writer, Link link, Object... namesAndValues) 096 { 097 addParameters(link); 098 099 element = writer.element("a", "href", buildHref(link)); 100 101 writer.attributes(namesAndValues); 102 103 resources.renderInformalParameters(writer); 104 105 this.link = link; 106 } 107 108 /** 109 * Adds any user-defined parameters as query parameters. 110 * @param link a {@link org.apache.tapestry5.Link}. 111 */ 112 protected final void addParameters(Link link) 113 { 114 if (!resources.isBound("parameters")) 115 return; 116 117 for(Map.Entry<String,?> entry : parameters.entrySet()) 118 { 119 String name = entry.getKey(); 120 121 // Per TAP5-2126, we want to override any prior value (typically, set from an ActivationRequestParameter) 122 // with the new value. 123 124 link.removeParameter(name); 125 126 link.addParameterValue(name, entry.getValue()); 127 } 128 } 129 130 /** 131 * Returns the most recently rendered {@link org.apache.tapestry5.Link} for this component. Subclasses calculate 132 * their link value as they render, and the value is valid until the end of the request, or the next time the same 133 * component renders itself (if inside a loop). 134 * 135 * @return the most recent link, or null 136 */ 137 public Link getLink() 138 { 139 return link; 140 } 141 142 /** 143 * Returns the unique client id for this element. This is valid only after the component has rendered (its start 144 * tag). A client id is generated the first time this method is invoked, after the link renders its start tag. 145 */ 146 public final String getClientId() 147 { 148 if (clientId == null) 149 { 150 if (element == null) 151 throw new IllegalStateException(String.format( 152 "Client id for %s is not available as it did not render yet (or was disabled).", 153 resources.getCompleteId())); 154 155 clientId = jsSupport.allocateClientId(resources); 156 157 element.forceAttributes("id", clientId); 158 } 159 160 return clientId; 161 } 162 163 /** 164 * Returns true if the component is disabled (as per its disabled parameter). Disabled link components should not 165 * render a tag, but should still render their body. 166 * @return <code>true</code> or <code>false</code>. 167 */ 168 public boolean isDisabled() 169 { 170 return disabled; 171 } 172 173 /** 174 * Used for testing. 175 */ 176 final void inject(String anchor, ComponentResources resources) 177 { 178 this.anchor = anchor; 179 this.resources = resources; 180 } 181}