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