    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    }