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                // linkComponent.renderAdditionalAttributes(writer, cycle);
099                
100                if (hasBody) {
101                    wrappedWriter.close();
102                    
103                    // Close the <element> tag
104                    
105                    writer.end();
106                } else 
107                    writer.closeTag();
108            }
109            
110            cycle.removeAttribute(Tapestry.LINK_COMPONENT_ATTRIBUTE_NAME);
111        }
112    
113        /**
114         * Converts the EngineServiceLink into a URI or URL. This implementation
115         * gets the scheme and anchor from the component (both of which may be
116         * null), and invokes
117         * {@link ILink#getURL(String, String, int, String, boolean)}.
118         */
119    
120        protected String constructURL(ILinkComponent component, IRequestCycle cycle)
121        {
122            ILink link = component.getLink(cycle);
123            
124            String scheme = component.getScheme();
125            Integer port = component.getPort();
126            int portI = (port == null) ? 0 : port.intValue();
127            String anchor = component.getAnchor();
128            
129            return link.getURL(scheme, null, portI, anchor, true);
130        }
131    
132        /**
133         * Invoked after the href attribute has been written but before the body of
134         * the link is rendered (but only if the link is not disabled).
135         * <p>
136         * This implementation does nothing.
137         * </p>
138         *
139         * @param writer
140         *          Markup writer.
141         * @param cycle
142         *          Current request cycle.
143         * @param link
144         *          The link component being rendered.
145         */
146    
147        protected void beforeBodyRender(IMarkupWriter writer, IRequestCycle cycle, ILinkComponent link)
148        {
149        }
150    
151        /**
152         * Invoked after the body of the link is rendered, but before
153         * {@link ILinkComponent#renderAdditionalAttributes(IMarkupWriter, IRequestCycle)}is
154         * invoked (but only if the link is not disabled).
155         * 
156         * <p>
157         * This implementation does nothing.
158         * </p>
159         * @param writer
160         *          Markup writer.
161         * @param cycle
162         *          Current request cycle.
163         * @param link
164         *          The link component being rendered.
165         */
166    
167        protected void afterBodyRender(IMarkupWriter writer, IRequestCycle cycle, ILinkComponent link)
168        {
169        }
170    
171        /**
172         * For {@link DirectLink} components only, manages writing out event handlers for link
173         * if any of the dynamic (async/json/etc) parameters are set on the component.
174         * 
175         * <p>
176         *  Will try to write the logic into the <code>onClick</code> attribute of the link 
177         *  if not bound, otherwise it will render it using the {@link DirectLink#getScript()} script.
178         * </p>
179         * 
180         * @param writer
181         *          The writer to render attributes into.
182         * @param cycle
183         *          The current request cycle.
184         * @param link
185         *          The component link being rendered for.
186         */
187        protected void renderAsyncParams(IMarkupWriter writer, IRequestCycle cycle, DirectLink link)
188        {
189            List comps = link.getUpdateComponents();
190            
191            if (!link.isAsync() && !link.isJson() 
192                    && (comps == null
193                    || comps.size() <= 0))
194                return;
195            
196            if (!link.isParameterBound("onclick") && !link.isParameterBound("onClick")) {
197                writer.attribute("onclick", 
198                        "return tapestry.linkOnClick(this.href,'" + link.getClientId() + "', " + link.isJson() + ")");
199                return;
200            }
201            
202            PageRenderSupport prs = TapestryUtils.getPageRenderSupport(cycle, link);
203            
204            if (prs == null)
205                return;
206            
207            Map parms = new HashMap();
208            
209            parms.put("component", link);
210            parms.put("json", Boolean.valueOf(link.isJson()));
211            parms.put("key", ScriptUtils.functionHash("onclick" + link.hashCode()));
212            
213            // execute script template
214            
215            link.getScript().execute(link, cycle, prs, parms);
216        }
217        
218        /** @since 3.0 * */
219    
220        protected String getElement()
221        {
222            return "a";
223        }
224    
225        protected String getUrlAttribute()
226        {
227            return "href";
228        }
229    
230        protected String getTargetAttribute()
231        {
232            return "target";
233        }
234    
235        protected boolean getHasBody()
236        {
237            return true;
238        }
239    }