001 // Copyright 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.CSSClassConstants; 019 import org.apache.tapestry5.ClientElement; 020 import org.apache.tapestry5.ComponentAction; 021 import org.apache.tapestry5.ComponentResources; 022 import org.apache.tapestry5.MarkupWriter; 023 import org.apache.tapestry5.annotations.Environmental; 024 import org.apache.tapestry5.annotations.Parameter; 025 import org.apache.tapestry5.annotations.SupportsInformalParameters; 026 import org.apache.tapestry5.corelib.internal.ComponentActionSink; 027 import org.apache.tapestry5.corelib.internal.FormSupportAdapter; 028 import org.apache.tapestry5.corelib.internal.HiddenFieldPositioner; 029 import org.apache.tapestry5.corelib.mixins.TriggerFragment; 030 import org.apache.tapestry5.dom.Element; 031 import org.apache.tapestry5.ioc.annotations.Inject; 032 import org.apache.tapestry5.services.ClientBehaviorSupport; 033 import org.apache.tapestry5.services.ClientDataEncoder; 034 import org.apache.tapestry5.services.Environment; 035 import org.apache.tapestry5.services.FormSupport; 036 import org.apache.tapestry5.services.HiddenFieldLocationRules; 037 import org.apache.tapestry5.services.javascript.JavaScriptSupport; 038 import org.slf4j.Logger; 039 040 /** 041 * A FormFragment is a portion of a Form that may be selectively displayed. Form elements inside a FormFragment will 042 * automatically bypass validation when the fragment is invisible. The trick is to also bypass server-side form 043 * processing for such fields when the form is submitted; client-side logic "removes" the 044 * {@link org.apache.tapestry5.corelib.components.Form#FORM_DATA form data} for the fragment if it is invisible when the 045 * form 046 * is submitted; alternately, client-side logic can simply remove the form fragment element (including its visible and 047 * hidden fields) to prevent server-side processing. 048 * <p/> 049 * The client-side element will now listen to two new event defined by client-side constants: 050 * <dl> 051 * <dt>Tapestry.CHANGE_VISIBILITY_EVENT</dt> 052 * <dd>Change the visiblity as per the event memo's visibility property. When the visiblity changes, the correct 053 * animation is executed.</dd> 054 * <dt>Tapestry.HIDE_AND_REMOVE_EVENT</dt> 055 * <dd>Hides the element, then removes it from the DOM entirely. 056 * </dl> 057 * 058 * @see TriggerFragment 059 * @see Form 060 * @tapestrydoc 061 */ 062 @SupportsInformalParameters 063 public class FormFragment implements ClientElement 064 { 065 /** 066 * Determines if the fragment is initially visible or initially invisible (the default). This is only used when 067 * rendering; when the form is submitted, the hidden field value is used to determine whether the elements within 068 * the fragment should be processed (or ignored if still invisible). 069 */ 070 @Parameter 071 private boolean visible; 072 073 /** 074 * If true, then the fragment submits the values from fields it contains <em>even if</em> the fragment is not 075 * visible. 076 * The default is to omit values from fields when the enclosing fragment is non visible. 077 * 078 * @since 5.2.0 079 */ 080 @Parameter 081 private boolean alwaysSubmit; 082 083 /** 084 * Name of a function on the client-side Tapestry.ElementEffect object that is invoked to make the fragment visible. 085 * If not specified, then the default "slidedown" function is used. 086 */ 087 @Parameter(defaultPrefix = BindingConstants.LITERAL) 088 private String show; 089 090 /** 091 * Name of a function on the client-side Tapestry.ElementEffect object that is invoked when the fragment is to be 092 * hidden. If not specified, the default "slideup" function is used. 093 */ 094 @Parameter(defaultPrefix = BindingConstants.LITERAL) 095 private String hide; 096 097 /** 098 * The element to render for each iteration of the loop. The default comes from the template, or "div" if the 099 * template did not specific an element. 100 */ 101 @Parameter(defaultPrefix = BindingConstants.LITERAL) 102 private String element; 103 104 /** 105 * If bound, then the id attribute of the rendered element will be this exact value. If not bound, then a unique id 106 * is generated for the element. 107 */ 108 @Parameter(name = "id", defaultPrefix = BindingConstants.LITERAL) 109 private String idParameter; 110 111 /** 112 * A javascript function that overrides the default visibility search bound. 113 * Tapestry normally ensures that not only the form fragment but all parent elements up to the containing form 114 * are visible when determining whether to submit the contents of a form fragment. This behavior can be modified by 115 * supplying a javascript function that receives the "current" element in the chain. Returning true will stop the 116 * search (and report "isDeepVisible" as true). Returning false will continue the search up the chain. 117 * @since 5.3 118 */ 119 @Parameter(defaultPrefix = BindingConstants.LITERAL, allowNull = false) 120 private String visibleBound; 121 122 @Inject 123 private Environment environment; 124 125 @Environmental 126 private JavaScriptSupport javascriptSupport; 127 128 @Inject 129 private ComponentResources resources; 130 131 @Environmental 132 private ClientBehaviorSupport clientBehaviorSupport; 133 134 private String clientId; 135 136 private ComponentActionSink componentActions; 137 138 @Inject 139 private Logger logger; 140 141 @Inject 142 private HiddenFieldLocationRules rules; 143 144 private HiddenFieldPositioner hiddenFieldPositioner; 145 146 @Inject 147 private ClientDataEncoder clientDataEncoder; 148 149 String defaultElement() 150 { 151 return resources.getElementName("div"); 152 } 153 154 /** 155 * Renders a <div> tag and provides an override of the {@link org.apache.tapestry5.services.FormSupport} 156 * environmental. 157 */ 158 void beginRender(MarkupWriter writer) 159 { 160 FormSupport formSupport = environment.peekRequired(FormSupport.class); 161 162 clientId = resources.isBound("id") ? idParameter : javascriptSupport.allocateClientId(resources); 163 164 hiddenFieldPositioner = new HiddenFieldPositioner(writer, rules); 165 166 Element element = writer.element(this.element, "id", clientId); 167 168 resources.renderInformalParameters(writer); 169 170 if (!visible) 171 element.addClassName(CSSClassConstants.INVISIBLE); 172 173 clientBehaviorSupport.addFormFragment(clientId, alwaysSubmit, show, hide, visibleBound); 174 175 componentActions = new ComponentActionSink(logger, clientDataEncoder); 176 177 // Here's the magic of environmentals ... we can create a wrapper around 178 // the normal FormSupport environmental that intercepts some of the behavior. 179 // Here we're setting aside all the actions inside the FormFragment so that we 180 // can control whether those actions occur when the form is submitted. 181 182 FormSupport override = new FormSupportAdapter(formSupport) 183 { 184 @Override 185 public <T> void store(T component, ComponentAction<T> action) 186 { 187 componentActions.store(component, action); 188 } 189 190 @Override 191 public <T> void storeAndExecute(T component, ComponentAction<T> action) 192 { 193 componentActions.store(component, action); 194 195 action.execute(component); 196 } 197 }; 198 199 // Tada! Now all the enclosed components will use our override of FormSupport, 200 // until we pop it off. 201 202 environment.push(FormSupport.class, override); 203 204 } 205 206 /** 207 * Closes the <div> tag and pops off the {@link org.apache.tapestry5.services.FormSupport} environmental 208 * override. 209 * 210 * @param writer 211 */ 212 void afterRender(MarkupWriter writer) 213 { 214 hiddenFieldPositioner.getElement().attributes("type", "hidden", 215 216 "name", Form.FORM_DATA, 217 218 "id", clientId + "-hidden", 219 220 "value", componentActions.getClientData()); 221 222 writer.end(); // div 223 224 environment.pop(FormSupport.class); 225 } 226 227 public String getClientId() 228 { 229 return clientId; 230 } 231 }