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 <a> 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}