001 // Copyright 2007, 2008, 2009 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
024 /**
025 * Provides base utilities for classes that generate clickable links.
026 */
027 @SupportsInformalParameters
028 public abstract class AbstractLink implements ClientElement
029 {
030 /**
031 * An anchor value to append to the generated URL (the hash separator will be added automatically).
032 */
033 @Parameter(defaultPrefix = BindingConstants.LITERAL)
034 private String anchor;
035
036 /**
037 * If true, then then no link element is rendered (and no informal parameters as well). The body is, however, still
038 * rendered.
039 */
040 @Parameter("false")
041 private boolean disabled;
042
043 @Inject
044 private ComponentResources resources;
045
046 @Inject
047 private RenderSupport renderSupport;
048
049 private Link link;
050
051 private Element element;
052
053 private String clientId;
054
055 private String buildHref(Link link)
056 {
057 String href = link.toURI();
058
059 if (anchor == null) return href;
060
061 return href + "#" + anchor;
062 }
063
064
065 @SetupRender
066 void resetElementAndClientId()
067 {
068 element = null;
069 clientId = null;
070 }
071
072 /**
073 * Writes an <a> element with the provided link as the href attribute. A call to {@link
074 * org.apache.tapestry5.MarkupWriter#end()} is <em>not</em> provided. Automatically appends an anchor if
075 * the component's anchor parameter is non-null. Informal parameters are rendered as well.
076 *
077 * @param writer to write markup to
078 * @param link the link that will form the href
079 * @param namesAndValues additional attributes to write
080 */
081 protected final void writeLink(MarkupWriter writer, Link link, Object... namesAndValues)
082 {
083 element = writer.element("a", "href", buildHref(link));
084
085 writer.attributes(namesAndValues);
086
087 resources.renderInformalParameters(writer);
088
089 this.link = link;
090 }
091
092 /**
093 * Returns the most recently rendered {@link org.apache.tapestry5.Link} for this component. Subclasses calculate
094 * their link value as they render, and the value is valid until the end of the request, or the next time the same
095 * component renders itself (if inside a loop).
096 *
097 * @return the most recent link, or null
098 */
099 public Link getLink()
100 {
101 return link;
102 }
103
104 /**
105 * Returns the unique client id for this element. This is valid only after the component has rendered (its start
106 * tag). A client id is generated the first time this method is invoked, after the link renders its start tag.
107 */
108 public final String getClientId()
109 {
110 if (clientId == null)
111 {
112 if (element == null)
113 throw new IllegalStateException(
114 String.format("Client id for %s is not available as it did not render yet (or was disabled).",
115 resources.getCompleteId()));
116
117 clientId = renderSupport.allocateClientId(resources);
118
119 element.forceAttributes("id", clientId);
120 }
121
122 return clientId;
123 }
124
125 /**
126 * Returns true if the component is disabled (as per its disabled parameter). Disabled link components should not
127 * render a tag, but should still render their body.
128 */
129 public boolean isDisabled()
130 {
131 return disabled;
132 }
133
134 /**
135 * Used for testing.
136 */
137 final void inject(String anchor, ComponentResources resources)
138 {
139 this.anchor = anchor;
140 this.resources = resources;
141 }
142 }