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    }