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