    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}