001    // Copyright 2007, 2008, 2009 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    
024    /**
025     * Provides base utilities for classes that generate clickable links.
026     */
027    @SupportsInformalParameters
028    public abstract class AbstractLink implements ClientElement
029    {
030        /**
031         * An anchor value to append to the generated URL (the hash separator will be added automatically).
032         */
033        @Parameter(defaultPrefix = BindingConstants.LITERAL)
034        private String anchor;
035    
036        /**
037         * If true, then then no link element is rendered (and no informal parameters as well). The body is, however, still
038         * rendered.
039         */
040        @Parameter("false")
041        private boolean disabled;
042    
043        @Inject
044        private ComponentResources resources;
045    
046        @Inject
047        private RenderSupport renderSupport;
048    
049        private Link link;
050    
051        private Element element;
052    
053        private String clientId;
054    
055        private String buildHref(Link link)
056        {
057            String href = link.toURI();
058    
059            if (anchor == null) return href;
060    
061            return href + "#" + anchor;
062        }
063    
064    
065        @SetupRender
066        void resetElementAndClientId()
067        {
068            element = null;
069            clientId = null;
070        }
071    
072        /**
073         * Writes an <a> element with the provided link as the href attribute.  A call to {@link
074         * org.apache.tapestry5.MarkupWriter#end()} is <em>not</em> provided.            Automatically appends an anchor if
075         * the component's anchor parameter is non-null.  Informal parameters are rendered as well.
076         *
077         * @param writer         to write markup to
078         * @param link           the link that will form the href
079         * @param namesAndValues additional attributes to write
080         */
081        protected final void writeLink(MarkupWriter writer, Link link, Object... namesAndValues)
082        {
083            element = writer.element("a", "href", buildHref(link));
084    
085            writer.attributes(namesAndValues);
086    
087            resources.renderInformalParameters(writer);
088    
089            this.link = link;
090        }
091    
092        /**
093         * Returns the most recently rendered {@link org.apache.tapestry5.Link} for this component.  Subclasses calculate
094         * their link value as they render, and the value is valid until the end of the request, or the next time the same
095         * component renders itself (if inside a loop).
096         *
097         * @return the most recent link, or null
098         */
099        public Link getLink()
100        {
101            return link;
102        }
103    
104        /**
105         * Returns the unique client id for this element. This is valid only after the component has rendered (its start
106         * tag).  A client id is generated the first time this method is invoked, after the link renders its start tag.
107         */
108        public final String getClientId()
109        {
110            if (clientId == null)
111            {
112                if (element == null)
113                    throw new IllegalStateException(
114                            String.format("Client id for %s is not available as it did not render yet (or was disabled).",
115                                          resources.getCompleteId()));
116    
117                clientId = renderSupport.allocateClientId(resources);
118    
119                element.forceAttributes("id", clientId);
120            }
121    
122            return clientId;
123        }
124    
125        /**
126         * Returns true if the component is disabled (as per its disabled parameter). Disabled link components should not
127         * render a tag, but should still render their body.
128         */
129        public boolean isDisabled()
130        {
131            return disabled;
132        }
133    
134        /**
135         * Used for testing.
136         */
137        final void inject(String anchor, ComponentResources resources)
138        {
139            this.anchor = anchor;
140            this.resources = resources;
141        }
142    }