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.link;
016    
017    import org.apache.hivemind.ApplicationRuntimeException;
018    import org.apache.hivemind.HiveMind;
019    import org.apache.tapestry.*;
020    import org.apache.tapestry.components.ILinkComponent;
021    import org.apache.tapestry.engine.ILink;
022    import org.apache.tapestry.util.ScriptUtils;
023    
024    import java.util.HashMap;
025    import java.util.List;
026    import java.util.Map;
027    
028    /**
029     * Default implementation of {@link org.apache.tapestry.link.ILinkRenderer},
030     * which does nothing special. Can be used as a base class to provide additional
031     * handling.
032     * 
033     * @since 3.0
034     */
035    
036    public class DefaultLinkRenderer implements ILinkRenderer
037    {
038    
039        /**
040         * A shared instance used as a default for any link that doesn't explicitly
041         * override.
042         */
043    
044        public static final ILinkRenderer SHARED_INSTANCE = new DefaultLinkRenderer();
045    
046        public void renderLink(IMarkupWriter writer, IRequestCycle cycle, ILinkComponent linkComponent)
047        {
048            IMarkupWriter wrappedWriter = null;
049            
050            if (cycle.getAttribute(Tapestry.LINK_COMPONENT_ATTRIBUTE_NAME) != null)
051                throw new ApplicationRuntimeException(LinkMessages.noNesting(), linkComponent, null, null);
052            
053            cycle.setAttribute(Tapestry.LINK_COMPONENT_ATTRIBUTE_NAME, linkComponent);
054            
055            boolean hasBody = getHasBody();
056    
057            boolean disabled = linkComponent.isDisabled() || cycle.isRewinding();
058    
059            if (!disabled)
060            {
061                if (hasBody)
062                    writer.begin(getElement());
063                else 
064                    writer.beginEmpty(getElement());
065                
066                linkComponent.renderAdditionalAttributes(writer, cycle);
067                
068                writer.attribute(getUrlAttribute(), constructURL(linkComponent, cycle));
069                
070                String target = linkComponent.getTarget();
071                
072                if (HiveMind.isNonBlank(target))
073                    writer.attribute(getTargetAttribute(), target);
074                
075                if (DirectLink.class.isInstance(linkComponent)) {
076                    DirectLink direct = (DirectLink)linkComponent;
077                    
078                    renderAsyncParams(writer, cycle, direct);
079                }
080                
081                beforeBodyRender(writer, cycle, linkComponent);
082                
083                // Allow the wrapped components a chance to render.
084                // Along the way, they may interact with this component
085                // and cause the name variable to get set.
086                
087                wrappedWriter = writer.getNestedWriter();
088            } else 
089                wrappedWriter = writer;
090            
091            if (hasBody) 
092                linkComponent.renderBody(wrappedWriter, cycle);
093            
094            if (!disabled) {
095                
096                afterBodyRender(writer, cycle, linkComponent);
097                            
098                if (hasBody) {
099                    wrappedWriter.close();
100                    
101                    // Close the <element> tag
102                    
103                    writer.end();
104                } else 
105                    writer.closeTag();
106            }
107            
108            cycle.removeAttribute(Tapestry.LINK_COMPONENT_ATTRIBUTE_NAME);
109        }
110    
111        /**
112         * Converts the EngineServiceLink into a URI or URL. This implementation
113         * gets the scheme and anchor from the component (both of which may be
114         * null), and invokes
115         * {@link ILink#getURL(String, String, int, String, boolean)}.
116         */
117    
118        protected String constructURL(ILinkComponent component, IRequestCycle cycle)
119        {
120            ILink link = component.getLink(cycle);
121            
122            String scheme = component.getScheme();
123            Integer port = component.getPort();
124            int portI = (port == null) ? 0 : port.intValue();
125            String anchor = component.getAnchor();
126            
127            return link.getURL(scheme, null, portI, anchor, true);
128        }
129    
130        /**
131         * Invoked after the href attribute has been written but before the body of
132         * the link is rendered (but only if the link is not disabled).
133         * <p>
134         * This implementation does nothing.
135         * </p>
136         *
137         * @param writer
138         *          Markup writer.
139         * @param cycle
140         *          Current request cycle.
141         * @param link
142         *          The link component being rendered.
143         */
144    
145        protected void beforeBodyRender(IMarkupWriter writer, IRequestCycle cycle, ILinkComponent link)
146        {
147        }
148    
149        /**
150         * Invoked after the body of the link is rendered, but before
151         * {@link ILinkComponent#renderAdditionalAttributes(IMarkupWriter, IRequestCycle)}is
152         * invoked (but only if the link is not disabled).
153         * 
154         * <p>
155         * This implementation does nothing.
156         * </p>
157         * @param writer
158         *          Markup writer.
159         * @param cycle
160         *          Current request cycle.
161         * @param link
162         *          The link component being rendered.
163         */
164    
165        protected void afterBodyRender(IMarkupWriter writer, IRequestCycle cycle, ILinkComponent link)
166        {
167        }
168    
169        /**
170         * For {@link DirectLink} components only, manages writing out event handlers for link
171         * if any of the dynamic (async/json/etc) parameters are set on the component.
172         * 
173         * <p>
174         *  Will try to write the logic into the <code>onClick</code> attribute of the link 
175         *  if not bound, otherwise it will render it using the {@link DirectLink#getScript()} script.
176         * </p>
177         * 
178         * @param writer
179         *          The writer to render attributes into.
180         * @param cycle
181         *          The current request cycle.
182         * @param link
183         *          The component link being rendered for.
184         */
185        protected void renderAsyncParams(IMarkupWriter writer, IRequestCycle cycle, DirectLink link)
186        {
187            List comps = link.getUpdateComponents();
188            
189            if (!link.isAsync() && !link.isJson() 
190                    && (comps == null
191                    || comps.size() <= 0))
192                return;
193            
194            if (!link.isParameterBound("onclick") && !link.isParameterBound("onClick")) {
195                writer.attribute("onclick", 
196                        "return tapestry.linkOnClick(this.href,'" + link.getClientId() + "', " + link.isJson() + ")");
197                return;
198            }
199            
200            PageRenderSupport prs = TapestryUtils.getPageRenderSupport(cycle, link);
201            
202            if (prs == null)
203                return;
204            
205            Map parms = new HashMap();
206            
207            parms.put("component", link);
208            parms.put("json", Boolean.valueOf(link.isJson()));
209            parms.put("key", ScriptUtils.functionHash("onclick" + link.hashCode()));
210            
211            // execute script template
212            
213            link.getScript().execute(link, cycle, prs, parms);
214        }
215        
216        /** @since 3.0 * */
217    
218        protected String getElement()
219        {
220            return "a";
221        }
222    
223        protected String getUrlAttribute()
224        {
225            return "href";
226        }
227    
228        protected String getTargetAttribute()
229        {
230            return "target";
231        }
232    
233        protected boolean getHasBody()
234        {
235            return true;
236        }
237    }