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
015package org.apache.tapestry.contrib.link;
016
017import org.apache.hivemind.ApplicationRuntimeException;
018import org.apache.tapestry.IMarkupWriter;
019import org.apache.tapestry.IRequestCycle;
020import org.apache.tapestry.Tapestry;
021import org.apache.tapestry.components.ILinkComponent;
022import org.apache.tapestry.engine.ILink;
023import org.apache.tapestry.html.Body;
024import org.apache.tapestry.link.DefaultLinkRenderer;
025import 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 */
049public 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}