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.contrib.link;
016
017 import org.apache.hivemind.ApplicationRuntimeException;
018 import org.apache.tapestry.*;
019 import org.apache.tapestry.components.ILinkComponent;
020 import org.apache.tapestry.engine.ILink;
021 import org.apache.tapestry.link.DefaultLinkRenderer;
022 import org.apache.tapestry.link.ILinkRenderer;
023
024 /**
025 * A link renderer that ensures that the generated link uses POST instead of GET
026 * request and is therefore no longer limited in size.
027 * <p>
028 * Theoretically, browsers should support very long URLs, but in practice they
029 * often start behaving strangely if the URLs are more than 256 characters. This
030 * renderer uses JavaScript to generate forms containing the requested link
031 * parameters and then "post" them when the link is selected. As a result, the
032 * data is sent to the server using a POST request with a very short URL and
033 * there is no longer a limitation in the size of the parameters.
034 * <p>
035 * In short, simply add the following parameter to your <code>DirectLink</code>,
036 * <code>ExternalLink</code>, or other such link components:
037 *
038 * <pre>
039 * renderer = "ognl: @org.apache.tapestry.contrib.link.FormLinkRenderer@RENDERER"
040 * </pre>
041 *
042 * and they will automatically start using POST rather than GET requests. Their
043 * parameters will no longer be limited in size.
044 * @author mb
045 * @since 4.0
046 */
047 public class FormLinkRenderer extends DefaultLinkRenderer
048 {
049
050 /**
051 * A public singleton instance of the <code>FormLinkRenderer</code>.
052 * <p>
053 * Since the <code>FormLinkRenderer</code> is stateless, this instance can
054 * serve all links within your application without interference.
055 */
056 public static final ILinkRenderer RENDERER = new FormLinkRenderer();
057
058 public void renderLink(IMarkupWriter writer, IRequestCycle cycle,
059 ILinkComponent linkComponent)
060 {
061 IMarkupWriter wrappedWriter = null;
062
063 if (cycle.getAttribute(Tapestry.LINK_COMPONENT_ATTRIBUTE_NAME) != null)
064 throw new ApplicationRuntimeException(Tapestry.getMessage("AbstractLinkComponent.no-nesting"),
065 linkComponent, null, null);
066
067 cycle.setAttribute(Tapestry.LINK_COMPONENT_ATTRIBUTE_NAME,
068 linkComponent);
069
070 String formName = cycle.getUniqueId("LinkForm");
071
072 boolean hasBody = getHasBody();
073 boolean disabled = linkComponent.isDisabled();
074
075 if (!disabled && !cycle.isRewinding())
076 {
077 ILink l = linkComponent.getLink(cycle);
078 String anchor = linkComponent.getAnchor();
079
080 PageRenderSupport prs = TapestryUtils.getPageRenderSupport(cycle, linkComponent);
081
082 String function = generateFormFunction(formName, l, anchor);
083 prs.addBodyScript(linkComponent, function);
084
085 if (hasBody)
086 writer.begin(getElement());
087 else
088 writer.beginEmpty(getElement());
089
090 writer.attribute(getUrlAttribute(), "javascript: document."
091 + formName + ".submit();");
092
093 beforeBodyRender(writer, cycle, linkComponent);
094
095 // Allow the wrapped components a chance to render.
096 // Along the way, they may interact with this component
097 // and cause the name variable to get set.
098
099 wrappedWriter = writer.getNestedWriter();
100 }
101 else
102 wrappedWriter = writer;
103
104 if (hasBody)
105 linkComponent.renderBody(wrappedWriter, cycle);
106
107 if (!disabled && !cycle.isRewinding())
108 {
109 afterBodyRender(writer, cycle, linkComponent);
110
111 linkComponent.renderAdditionalAttributes(writer, cycle);
112
113 if (hasBody)
114 {
115 wrappedWriter.close();
116
117 // Close the <element> tag
118
119 writer.end();
120 }
121 else writer.closeTag();
122 }
123
124 cycle.removeAttribute(Tapestry.LINK_COMPONENT_ATTRIBUTE_NAME);
125 }
126
127 private String generateFormFunction(String formName, ILink link,
128 String anchor)
129 {
130 String[] parameterNames = link.getParameterNames();
131
132 StringBuffer buf = new StringBuffer();
133 buf.append("function prepare" + formName + "() {\n");
134
135 buf.append(" var html = \"\";\n");
136 buf.append(" html += \"<div style='position: absolute'>\";\n");
137
138 String url = link.getURL(anchor, false);
139 buf.append(" html += \"<form name='" + formName
140 + "' method='post' action='" + url + "'>\";\n");
141
142 for(int i = 0; i < parameterNames.length; i++)
143 {
144 String parameter = parameterNames[i];
145 String[] values = link.getParameterValues(parameter);
146 if (values != null) {
147 for (int j = 0; j < values.length; j++) {
148 String value = values[j];
149 buf.append(" html += \"<input type='hidden' name='" + parameter + "' value='" + value + "'/>\";\n");
150 }
151 }
152 }
153 buf.append(" html += \"<\" + \"/form>\";\n");
154 buf.append(" html += \"<\" + \"/div>\";\n");
155 buf.append(" document.write(html);\n");
156 buf.append("}\n");
157
158 buf.append("prepare" + formName + "();\n\n");
159
160 return buf.toString();
161 }
162
163 }