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