001    // Copyright 2007, 2008, 2009, 2011 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.tapestry5.corelib.base;
016    
017    import org.apache.tapestry5.*;
018    import org.apache.tapestry5.annotations.Parameter;
019    import org.apache.tapestry5.annotations.SetupRender;
020    import org.apache.tapestry5.annotations.SupportsInformalParameters;
021    import org.apache.tapestry5.dom.Element;
022    import org.apache.tapestry5.ioc.annotations.Inject;
023    import org.apache.tapestry5.services.javascript.JavaScriptSupport;
024    
025    import java.util.Map;
026    
027    /**
028     * Provides base utilities for classes that generate clickable links.
029     */
030    @SupportsInformalParameters
031    public abstract class AbstractLink implements ClientElement
032    {
033        /**
034         * An anchor value to append to the generated URL (the hash separator will be added automatically).
035         */
036        @Parameter(defaultPrefix = BindingConstants.LITERAL)
037        private String anchor;
038    
039        /**
040         * If true, then then no link element is rendered (and no informal parameters as well). The body is, however, still
041         * rendered.
042         */
043        @Parameter("false")
044        private boolean disabled;
045    
046        /**
047         * If specified, the parameters are added to the link as query parameters in key=value fashion.
048         * Values will be coerced to string using value encoder; keys should be Strings.
049         * @since 5.3
050         */
051        @Parameter(allowNull = false)
052        private Map<String, ?> parameters;
053    
054        @Inject
055        private ComponentResources resources;
056    
057        @Inject
058        private JavaScriptSupport jsSupport;
059    
060        private Link link;
061    
062        private Element element;
063    
064        private String clientId;
065    
066        private String buildHref(Link link)
067        {
068            String href = link.toURI();
069    
070            if (anchor == null)
071                return href;
072    
073            return href + "#" + anchor;
074        }
075    
076        @SetupRender
077        void resetElementAndClientId()
078        {
079            element = null;
080            clientId = null;
081        }
082    
083        /**
084         * Writes an &lt;a&gt; element with the provided link as the href attribute. A call to
085         * {@link org.apache.tapestry5.MarkupWriter#end()} is <em>not</em> provided. Automatically appends an anchor if
086         * the component's anchor parameter is non-null. Informal parameters are rendered as well.
087         * 
088         * @param writer
089         *            to write markup to
090         * @param link
091         *            the link that will form the href
092         * @param namesAndValues
093         *            additional attributes to write
094         */
095        protected final void writeLink(MarkupWriter writer, Link link, Object... namesAndValues)
096        {
097            addParameters(link);
098    
099            element = writer.element("a", "href", buildHref(link));
100    
101            writer.attributes(namesAndValues);
102    
103            resources.renderInformalParameters(writer);
104    
105            this.link = link;
106        }
107    
108        /**
109         * Adds any user-defined parameters as query parameters.
110         * @param link
111         */
112        protected final void addParameters(Link link)
113        {
114           if (!resources.isBound("parameters"))
115               return;
116    
117           for(Map.Entry<String,?> entry : parameters.entrySet())
118           {
119               link.addParameterValue(entry.getKey(), entry.getValue());
120           }
121        }
122    
123        /**
124         * Returns the most recently rendered {@link org.apache.tapestry5.Link} for this component. Subclasses calculate
125         * their link value as they render, and the value is valid until the end of the request, or the next time the same
126         * component renders itself (if inside a loop).
127         * 
128         * @return the most recent link, or null
129         */
130        public Link getLink()
131        {
132            return link;
133        }
134    
135        /**
136         * Returns the unique client id for this element. This is valid only after the component has rendered (its start
137         * tag). A client id is generated the first time this method is invoked, after the link renders its start tag.
138         */
139        public final String getClientId()
140        {
141            if (clientId == null)
142            {
143                if (element == null)
144                    throw new IllegalStateException(String.format(
145                            "Client id for %s is not available as it did not render yet (or was disabled).",
146                            resources.getCompleteId()));
147    
148                clientId = jsSupport.allocateClientId(resources);
149    
150                element.forceAttributes("id", clientId);
151            }
152    
153            return clientId;
154        }
155    
156        /**
157         * Returns true if the component is disabled (as per its disabled parameter). Disabled link components should not
158         * render a tag, but should still render their body.
159         */
160        public boolean isDisabled()
161        {
162            return disabled;
163        }
164    
165        /**
166         * Used for testing.
167         */
168        final void inject(String anchor, ComponentResources resources)
169        {
170            this.anchor = anchor;
171            this.resources = resources;
172        }
173    }