001 // Copyright 2007, 2008, 2009, 2011 The 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
015 package org.apache.tapestry5.corelib.base;
016
017 import org.apache.tapestry5.*;
018 import org.apache.tapestry5.annotations.Parameter;
019 import org.apache.tapestry5.annotations.SetupRender;
020 import org.apache.tapestry5.annotations.SupportsInformalParameters;
021 import org.apache.tapestry5.dom.Element;
022 import org.apache.tapestry5.ioc.annotations.Inject;
023 import org.apache.tapestry5.services.javascript.JavaScriptSupport;
024
025 import java.util.Map;
026
027 /**
028 * Provides base utilities for classes that generate clickable links.
029 */
030 @SupportsInformalParameters
031 public 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 private 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
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 link.addParameterValue(entry.getKey(), entry.getValue());
120 }
121 }
122
123 /**
124 * Returns the most recently rendered {@link org.apache.tapestry5.Link} for this component. Subclasses calculate
125 * their link value as they render, and the value is valid until the end of the request, or the next time the same
126 * component renders itself (if inside a loop).
127 *
128 * @return the most recent link, or null
129 */
130 public Link getLink()
131 {
132 return link;
133 }
134
135 /**
136 * Returns the unique client id for this element. This is valid only after the component has rendered (its start
137 * tag). A client id is generated the first time this method is invoked, after the link renders its start tag.
138 */
139 public final String getClientId()
140 {
141 if (clientId == null)
142 {
143 if (element == null)
144 throw new IllegalStateException(String.format(
145 "Client id for %s is not available as it did not render yet (or was disabled).",
146 resources.getCompleteId()));
147
148 clientId = jsSupport.allocateClientId(resources);
149
150 element.forceAttributes("id", clientId);
151 }
152
153 return clientId;
154 }
155
156 /**
157 * Returns true if the component is disabled (as per its disabled parameter). Disabled link components should not
158 * render a tag, but should still render their body.
159 */
160 public boolean isDisabled()
161 {
162 return disabled;
163 }
164
165 /**
166 * Used for testing.
167 */
168 final void inject(String anchor, ComponentResources resources)
169 {
170 this.anchor = anchor;
171 this.resources = resources;
172 }
173 }