001// Copyright 2007-2013The 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.tapestry5.corelib.base;
016
017import org.apache.tapestry5.*;
018import org.apache.tapestry5.annotations.Parameter;
019import org.apache.tapestry5.annotations.SetupRender;
020import org.apache.tapestry5.annotations.SupportsInformalParameters;
021import org.apache.tapestry5.dom.Element;
022import org.apache.tapestry5.ioc.annotations.Inject;
023import org.apache.tapestry5.services.javascript.JavaScriptSupport;
024
025import java.util.Map;
026
027/**
028 * Provides base utilities for classes that generate clickable links.
029 */
030@SupportsInformalParameters
031public 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    protected 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 a {@link org.apache.tapestry5.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           String name = entry.getKey();
120
121           // Per TAP5-2126, we want to override any prior value (typically, set from an ActivationRequestParameter)
122           // with the new value.
123
124           link.removeParameter(name);
125
126           link.addParameterValue(name, entry.getValue());
127       }
128    }
129
130    /**
131     * Returns the most recently rendered {@link org.apache.tapestry5.Link} for this component. Subclasses calculate
132     * their link value as they render, and the value is valid until the end of the request, or the next time the same
133     * component renders itself (if inside a loop).
134     * 
135     * @return the most recent link, or null
136     */
137    public Link getLink()
138    {
139        return link;
140    }
141
142    /**
143     * Returns the unique client id for this element. This is valid only after the component has rendered (its start
144     * tag). A client id is generated the first time this method is invoked, after the link renders its start tag.
145     */
146    public final String getClientId()
147    {
148        if (clientId == null)
149        {
150            if (element == null)
151                throw new IllegalStateException(String.format(
152                        "Client id for %s is not available as it did not render yet (or was disabled).",
153                        resources.getCompleteId()));
154
155            clientId = jsSupport.allocateClientId(resources);
156
157            element.forceAttributes("id", clientId);
158        }
159
160        return clientId;
161    }
162
163    /**
164     * Returns true if the component is disabled (as per its disabled parameter). Disabled link components should not
165     * render a tag, but should still render their body.
166     * @return <code>true</code> or <code>false</code>.
167     */
168    public boolean isDisabled()
169    {
170        return disabled;
171    }
172
173    /**
174     * Used for testing.
175     */
176    final void inject(String anchor, ComponentResources resources)
177    {
178        this.anchor = anchor;
179        this.resources = resources;
180    }
181}