001 // Copyright 2007, 2008, 2009, 2010, 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.components; 016 017 import org.apache.tapestry5.BindingConstants; 018 import org.apache.tapestry5.Block; 019 import org.apache.tapestry5.CSSClassConstants; 020 import org.apache.tapestry5.ClientBodyElement; 021 import org.apache.tapestry5.ComponentAction; 022 import org.apache.tapestry5.ComponentResources; 023 import org.apache.tapestry5.ComponentParameterConstants; 024 import org.apache.tapestry5.MarkupWriter; 025 import org.apache.tapestry5.QueryParameterConstants; 026 import org.apache.tapestry5.annotations.BeginRender; 027 import org.apache.tapestry5.annotations.Environmental; 028 import org.apache.tapestry5.annotations.Parameter; 029 import org.apache.tapestry5.annotations.SupportsInformalParameters; 030 import org.apache.tapestry5.corelib.internal.ComponentActionSink; 031 import org.apache.tapestry5.corelib.internal.FormSupportAdapter; 032 import org.apache.tapestry5.corelib.internal.HiddenFieldPositioner; 033 import org.apache.tapestry5.dom.Element; 034 import org.apache.tapestry5.ioc.annotations.Inject; 035 import org.apache.tapestry5.services.ClientBehaviorSupport; 036 import org.apache.tapestry5.services.ClientDataEncoder; 037 import org.apache.tapestry5.services.Environment; 038 import org.apache.tapestry5.services.FormSupport; 039 import org.apache.tapestry5.services.Heartbeat; 040 import org.apache.tapestry5.services.HiddenFieldLocationRules; 041 import org.apache.tapestry5.services.javascript.JavaScriptSupport; 042 import org.slf4j.Logger; 043 044 /** 045 * A Zone is portion of the output page designed for easy dynamic updating via Ajax or other client-side effects. A 046 * Zone renders out as a <div> element (or whatever is specified in the template) and may have content initially, 047 * or may only get its content as a result of client side activity. 048 * <p/> 049 * Often, Zones are initially invisible, in which case the visible parameter may be set to false (it defaults to true). 050 * <p/> 051 * When a user clicks an {@link org.apache.tapestry5.corelib.components.ActionLink} whose zone parameter is set, the 052 * corresponding client-side Tapestry.ZoneManager object is located. It will update the content of the Zone's 053 * <div> and then invoke either a show method (if the div is not visible) or an update method (if the div is 054 * visible). The show and update parameters are the <em>names</em> of functions attached to the Tapestry.ElementEffect 055 * object. Likewise, a {@link org.apache.tapestry5.corelib.components.Form} component may also trigger an update of a 056 * client-side Zone. 057 * <p/> 058 * The server side event handler can return a {@link org.apache.tapestry5.Block} or a component to render as the new 059 * content on the client side. Often, re-rendering the Zone's {@linkplain #getBody() body} is useful. Multiple 060 * client-side zones may be updated by returning a {@link org.apache.tapestry5.ajax.MultiZoneUpdate}. 061 * <p/> 062 * Renders informal parameters, adding CSS class "t-zone" and possibly, "t-invisible". 063 * <p/> 064 * You will often want to specify the id parameter of the Zone, in addition to it's Tapestry component id; this "locks 065 * down" the client-side id, so the same value is used even in later partial renders of the page (essential if the Zone 066 * is nested inside another Zone). When you specify the client-side id, it is used exactly as provided (meaning that you 067 * are responsible for ensuring that there will not be an id conflict even in the face of multiple partial renders of 068 * the page). Failure to provide an explicit id results in a new, and non-predictable, id being generated for each 069 * partial render, which will often result in client-side failures to locate the element to update when the Zone is 070 * triggered. 071 * <p> 072 * In some cases, you may want to know (on the server side) the client id of the zone that was updated; this is passed 073 * as part of the Ajax request, as the {@link QueryParameterConstants#ZONE_ID} parameter. An example use of this would 074 * be to provide new content into a Zone that updates the same Zone, when the Zone's client-side id is dynamically 075 * allocated (rather than statically defined). In most cases, however, the programmer is responsible for assigning a 076 * specific client-side id, via the id parameter. 077 * <p/> 078 * A Zone starts and stops a {@link Heartbeat} when it renders (both normally, and when re-rendering). 079 * <p/> 080 * After the client-side content is updated, a client-side event is fired on the zone's element. The constant 081 * Tapestry.ZONE_UPDATED_EVENT can be used to listen to the event. 082 * 083 * @tapestrydoc 084 * @see AjaxFormLoop 085 * @see FormFragment 086 */ 087 @SupportsInformalParameters 088 public class Zone implements ClientBodyElement 089 { 090 /** 091 * Name of a function on the client-side Tapestry.ElementEffect object that is invoked to make the Zone's 092 * <div> visible before being updated. If not specified, then the basic "show" method is used. 093 */ 094 @Parameter(defaultPrefix = BindingConstants.LITERAL, 095 value = BindingConstants.SYMBOL + ":" + ComponentParameterConstants.ZONE_SHOW_METHOD) 096 private String show; 097 098 /** 099 * Name of a function on the client-side Tapestry.ElementEffect object that is invoked after the Zone's content has 100 * been updated. If not specified, then the basic "highlight" method is used, which performs a classic "yellow fade" 101 * to indicate to the user that and update has taken place. 102 */ 103 @Parameter(defaultPrefix = BindingConstants.LITERAL, 104 value = BindingConstants.SYMBOL + ":" + ComponentParameterConstants.ZONE_UPDATE_METHOD) 105 private String update; 106 107 /** 108 * The element name to render for the zone; this defaults to the element actually used in the template, or "div" if 109 * no specific element was specified. 110 */ 111 @Parameter(required = true, allowNull = false, defaultPrefix = BindingConstants.LITERAL) 112 private String elementName; 113 114 /** 115 * If bound, then the id attribute of the rendered element will be this exact value. If not bound, then a unique id 116 * is generated for the element. 117 */ 118 @Parameter(name = "id", defaultPrefix = BindingConstants.LITERAL) 119 private String idParameter; 120 121 @Environmental 122 private JavaScriptSupport javascriptSupport; 123 124 @Environmental 125 private ClientBehaviorSupport clientBehaviorSupport; 126 127 @Inject 128 private Environment environment; 129 130 /** 131 * If true (the default) then the zone will render normally. If false, then the "t-invisible" CSS class is added, 132 * which will make the zone initially invisible. 133 */ 134 @Parameter 135 private boolean visible = true; 136 137 @Inject 138 private ComponentResources resources; 139 140 @Inject 141 private Heartbeat heartbeat; 142 143 @Inject 144 private Logger logger; 145 146 @Inject 147 private ClientDataEncoder clientDataEncoder; 148 149 @Inject 150 private HiddenFieldLocationRules rules; 151 152 private String clientId; 153 154 private boolean insideForm; 155 156 private HiddenFieldPositioner hiddenFieldPositioner; 157 158 private ComponentActionSink actionSink; 159 160 String defaultElementName() 161 { 162 return resources.getElementName("div"); 163 } 164 165 void beginRender(MarkupWriter writer) 166 { 167 clientId = resources.isBound("id") ? idParameter : javascriptSupport.allocateClientId(resources); 168 169 Element e = writer.element(elementName, "id", clientId); 170 171 resources.renderInformalParameters(writer); 172 173 e.addClassName("t-zone"); 174 175 if (!visible) 176 e.addClassName(CSSClassConstants.INVISIBLE); 177 178 clientBehaviorSupport.addZone(clientId, show, update); 179 180 FormSupport existingFormSupport = environment.peek(FormSupport.class); 181 182 insideForm = existingFormSupport != null; 183 184 if (insideForm) 185 { 186 hiddenFieldPositioner = new HiddenFieldPositioner(writer, rules); 187 188 actionSink = new ComponentActionSink(logger, clientDataEncoder); 189 190 environment.push(FormSupport.class, new FormSupportAdapter(existingFormSupport) 191 { 192 @Override 193 public <T> void store(T component, ComponentAction<T> action) 194 { 195 actionSink.store(component, action); 196 } 197 198 @Override 199 public <T> void storeAndExecute(T component, ComponentAction<T> action) 200 { 201 store(component, action); 202 203 action.execute(component); 204 } 205 206 }); 207 } 208 209 heartbeat.begin(); 210 } 211 212 void afterRender(MarkupWriter writer) 213 { 214 heartbeat.end(); 215 216 if (insideForm) 217 { 218 environment.pop(FormSupport.class); 219 220 if (actionSink.isEmpty()) 221 { 222 hiddenFieldPositioner.discard(); 223 } 224 else 225 { 226 hiddenFieldPositioner.getElement().attributes("type", "hidden", 227 228 "name", Form.FORM_DATA, 229 230 "value", actionSink.getClientData()); 231 } 232 } 233 234 writer.end(); // div 235 } 236 237 /** 238 * The client id of the Zone; this is set when the Zone renders and will either be the value bound to the id 239 * parameter, or an allocated unique id. When the id parameter is bound, this value is always accurate. 240 * When the id parameter is not bound, the clientId is set during the {@linkplain BeginRender begin render phase} 241 * and will be null or inaccurate before then. 242 * 243 * @return client-side element id 244 */ 245 public String getClientId() 246 { 247 if (resources.isBound("id")) 248 return idParameter; 249 250 return clientId; 251 } 252 253 /** 254 * Returns the zone's body (the content enclosed by its start and end tags). This is often used as part of an Ajax 255 * partial page render to update the client with a fresh render of the content inside the zone. 256 * 257 * @return the zone's body as a Block 258 */ 259 public Block getBody() 260 { 261 return resources.getBody(); 262 } 263 }