001 // Copyright 2004, 2005 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.tapestry.link;
016
017 import org.apache.hivemind.ApplicationRuntimeException;
018 import org.apache.hivemind.HiveMind;
019 import org.apache.tapestry.*;
020 import org.apache.tapestry.components.ILinkComponent;
021 import org.apache.tapestry.engine.ILink;
022 import org.apache.tapestry.util.ScriptUtils;
023
024 import java.util.HashMap;
025 import java.util.List;
026 import java.util.Map;
027
028 /**
029 * Default implementation of {@link org.apache.tapestry.link.ILinkRenderer},
030 * which does nothing special. Can be used as a base class to provide additional
031 * handling.
032 *
033 * @since 3.0
034 */
035
036 public class DefaultLinkRenderer implements ILinkRenderer
037 {
038
039 /**
040 * A shared instance used as a default for any link that doesn't explicitly
041 * override.
042 */
043
044 public static final ILinkRenderer SHARED_INSTANCE = new DefaultLinkRenderer();
045
046 public void renderLink(IMarkupWriter writer, IRequestCycle cycle, ILinkComponent linkComponent)
047 {
048 IMarkupWriter wrappedWriter = null;
049
050 if (cycle.getAttribute(Tapestry.LINK_COMPONENT_ATTRIBUTE_NAME) != null)
051 throw new ApplicationRuntimeException(LinkMessages.noNesting(), linkComponent, null, null);
052
053 cycle.setAttribute(Tapestry.LINK_COMPONENT_ATTRIBUTE_NAME, linkComponent);
054
055 boolean hasBody = getHasBody();
056
057 boolean disabled = linkComponent.isDisabled() || cycle.isRewinding();
058
059 if (!disabled)
060 {
061 if (hasBody)
062 writer.begin(getElement());
063 else
064 writer.beginEmpty(getElement());
065
066 linkComponent.renderAdditionalAttributes(writer, cycle);
067
068 writer.attribute(getUrlAttribute(), constructURL(linkComponent, cycle));
069
070 String target = linkComponent.getTarget();
071
072 if (HiveMind.isNonBlank(target))
073 writer.attribute(getTargetAttribute(), target);
074
075 if (DirectLink.class.isInstance(linkComponent)) {
076 DirectLink direct = (DirectLink)linkComponent;
077
078 renderAsyncParams(writer, cycle, direct);
079 }
080
081 beforeBodyRender(writer, cycle, linkComponent);
082
083 // Allow the wrapped components a chance to render.
084 // Along the way, they may interact with this component
085 // and cause the name variable to get set.
086
087 wrappedWriter = writer.getNestedWriter();
088 } else
089 wrappedWriter = writer;
090
091 if (hasBody)
092 linkComponent.renderBody(wrappedWriter, cycle);
093
094 if (!disabled) {
095
096 afterBodyRender(writer, cycle, linkComponent);
097
098 if (hasBody) {
099 wrappedWriter.close();
100
101 // Close the <element> tag
102
103 writer.end();
104 } else
105 writer.closeTag();
106 }
107
108 cycle.removeAttribute(Tapestry.LINK_COMPONENT_ATTRIBUTE_NAME);
109 }
110
111 /**
112 * Converts the EngineServiceLink into a URI or URL. This implementation
113 * gets the scheme and anchor from the component (both of which may be
114 * null), and invokes
115 * {@link ILink#getURL(String, String, int, String, boolean)}.
116 */
117
118 protected String constructURL(ILinkComponent component, IRequestCycle cycle)
119 {
120 ILink link = component.getLink(cycle);
121
122 String scheme = component.getScheme();
123 Integer port = component.getPort();
124 int portI = (port == null) ? 0 : port.intValue();
125 String anchor = component.getAnchor();
126
127 return link.getURL(scheme, null, portI, anchor, true);
128 }
129
130 /**
131 * Invoked after the href attribute has been written but before the body of
132 * the link is rendered (but only if the link is not disabled).
133 * <p>
134 * This implementation does nothing.
135 * </p>
136 *
137 * @param writer
138 * Markup writer.
139 * @param cycle
140 * Current request cycle.
141 * @param link
142 * The link component being rendered.
143 */
144
145 protected void beforeBodyRender(IMarkupWriter writer, IRequestCycle cycle, ILinkComponent link)
146 {
147 }
148
149 /**
150 * Invoked after the body of the link is rendered, but before
151 * {@link ILinkComponent#renderAdditionalAttributes(IMarkupWriter, IRequestCycle)}is
152 * invoked (but only if the link is not disabled).
153 *
154 * <p>
155 * This implementation does nothing.
156 * </p>
157 * @param writer
158 * Markup writer.
159 * @param cycle
160 * Current request cycle.
161 * @param link
162 * The link component being rendered.
163 */
164
165 protected void afterBodyRender(IMarkupWriter writer, IRequestCycle cycle, ILinkComponent link)
166 {
167 }
168
169 /**
170 * For {@link DirectLink} components only, manages writing out event handlers for link
171 * if any of the dynamic (async/json/etc) parameters are set on the component.
172 *
173 * <p>
174 * Will try to write the logic into the <code>onClick</code> attribute of the link
175 * if not bound, otherwise it will render it using the {@link DirectLink#getScript()} script.
176 * </p>
177 *
178 * @param writer
179 * The writer to render attributes into.
180 * @param cycle
181 * The current request cycle.
182 * @param link
183 * The component link being rendered for.
184 */
185 protected void renderAsyncParams(IMarkupWriter writer, IRequestCycle cycle, DirectLink link)
186 {
187 List comps = link.getUpdateComponents();
188
189 if (!link.isAsync() && !link.isJson()
190 && (comps == null
191 || comps.size() <= 0))
192 return;
193
194 if (!link.isParameterBound("onclick") && !link.isParameterBound("onClick")) {
195 writer.attribute("onclick",
196 "return tapestry.linkOnClick(this.href,'" + link.getClientId() + "', " + link.isJson() + ")");
197 return;
198 }
199
200 PageRenderSupport prs = TapestryUtils.getPageRenderSupport(cycle, link);
201
202 if (prs == null)
203 return;
204
205 Map parms = new HashMap();
206
207 parms.put("component", link);
208 parms.put("json", Boolean.valueOf(link.isJson()));
209 parms.put("key", ScriptUtils.functionHash("onclick" + link.hashCode()));
210
211 // execute script template
212
213 link.getScript().execute(link, cycle, prs, parms);
214 }
215
216 /** @since 3.0 * */
217
218 protected String getElement()
219 {
220 return "a";
221 }
222
223 protected String getUrlAttribute()
224 {
225 return "href";
226 }
227
228 protected String getTargetAttribute()
229 {
230 return "target";
231 }
232
233 protected boolean getHasBody()
234 {
235 return true;
236 }
237 }