    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}