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