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 }