001// Copyright 2008-2014 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 015package org.apache.tapestry5.corelib.components; 016 017import org.apache.tapestry5.*; 018import org.apache.tapestry5.annotations.Environmental; 019import org.apache.tapestry5.annotations.Import; 020import org.apache.tapestry5.annotations.Parameter; 021import org.apache.tapestry5.annotations.SupportsInformalParameters; 022import org.apache.tapestry5.corelib.internal.ComponentActionSink; 023import org.apache.tapestry5.corelib.internal.FormSupportAdapter; 024import org.apache.tapestry5.corelib.internal.HiddenFieldPositioner; 025import org.apache.tapestry5.corelib.mixins.TriggerFragment; 026import org.apache.tapestry5.dom.Element; 027import org.apache.tapestry5.ioc.annotations.Inject; 028import org.apache.tapestry5.services.ClientDataEncoder; 029import org.apache.tapestry5.services.Environment; 030import org.apache.tapestry5.services.FormSupport; 031import org.apache.tapestry5.services.HiddenFieldLocationRules; 032import org.apache.tapestry5.services.javascript.JavaScriptSupport; 033import org.slf4j.Logger; 034 035/** 036 * A FormFragment is a portion of a Form that may be selectively displayed. Form elements inside a FormFragment will 037 * automatically bypass validation when the fragment is invisible. The trick is to also bypass server-side form 038 * processing for such fields when the form is submitted; client-side logic "removes" the 039 * {@link org.apache.tapestry5.corelib.components.Form#FORM_DATA form data} for the fragment if it is invisible when the 040 * form is submitted (e.g., the hidden form field is disabled); 041 * alternately, client-side logic can simply remove the form fragment element (including its visible and 042 * hidden fields) to prevent server-side processing. 043 * <p/> 044 * The client-side element will now listen to two new events defined by client-side constants: 045 * <dl> 046 * <dt>core/events.formfragment.changeVisibility or Tapestry.CHANGE_VISIBILITY_EVENT</dt> 047 * <dd>Change the visibility as per the event memo's visibility property. When the visibility changes, the correct 048 * animation is executed.</dd> 049 * <dt>core/events.formfragment.remove or Tapestry.HIDE_AND_REMOVE_EVENT</dt> 050 * <dd>Hides the element, then removes it from the DOM entirely. 051 * </dl> 052 * 053 * @tapestrydoc 054 * @see TriggerFragment 055 * @see Form 056 */ 057@SupportsInformalParameters 058@Import(module = "t5/core/form-fragment") 059public class FormFragment implements ClientElement 060{ 061 /** 062 * Determines if the fragment is initially visible or initially invisible (the default). This is only used when 063 * rendering; when the form is submitted, the hidden field value is used to determine whether the elements within 064 * the fragment should be processed (or ignored if still invisible). 065 */ 066 @Parameter 067 private boolean visible; 068 069 /** 070 * If true, then the fragment submits the values from fields it contains <em>even if</em> the fragment is not 071 * visible. 072 * The default is to omit values from fields when the enclosing fragment is non visible. 073 * 074 * @since 5.2.0 075 */ 076 @Parameter 077 private boolean alwaysSubmit; 078 079 /** 080 * Name of a function on the client-side Tapestry.ElementEffect object that is invoked to make the fragment visible. 081 * This is no longer used. 082 * 083 * @deprecated Deprecated in 5.4; clients that wish to animate should handle the <code>events.element.didShow</code> client-side event. 084 */ 085 @Parameter(defaultPrefix = BindingConstants.LITERAL) 086 private String show; 087 088 /** 089 * Name of a function on the client-side Tapestry.ElementEffect object that is invoked when the fragment is to be 090 * hidden. This is no longer used. 091 * 092 * @deprecated Deprecated in 5.4; clients that wish to animate should handle the <code>events.element.didHide</code> client-side event. 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 * The name of 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 body 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 ElementWrapper.deepVisible() as true). Returning false will continue the search up the chain. 117 * 118 * @since 5.3 119 * @deprecated Deprecated in 5.4 with no current replacement. 120 */ 121 @Parameter(defaultPrefix = BindingConstants.LITERAL, allowNull = false) 122 private String visibleBound; 123 124 @Inject 125 private Environment environment; 126 127 @Environmental 128 private JavaScriptSupport javascriptSupport; 129 130 @Inject 131 private ComponentResources resources; 132 133 private String clientId; 134 135 private ComponentActionSink componentActions; 136 137 @Inject 138 private Logger logger; 139 140 @Inject 141 private HiddenFieldLocationRules rules; 142 143 private HiddenFieldPositioner hiddenFieldPositioner; 144 145 @Inject 146 private ClientDataEncoder clientDataEncoder; 147 148 String defaultElement() 149 { 150 return resources.getElementName("div"); 151 } 152 153 /** 154 * Renders a <div> tag and provides an override of the {@link org.apache.tapestry5.services.FormSupport} 155 * environmental. 156 */ 157 void beginRender(MarkupWriter writer) 158 { 159 FormSupport formSupport = environment.peekRequired(FormSupport.class); 160 161 clientId = resources.isBound("id") ? idParameter : javascriptSupport.allocateClientId(resources); 162 163 hiddenFieldPositioner = new HiddenFieldPositioner(writer, rules); 164 165 Element element = writer.element(this.element, 166 "id", clientId, 167 "data-component-type", "core/FormFragment"); 168 169 resources.renderInformalParameters(writer); 170 171 if (!visible) 172 { 173 element.attribute("style", "display: none;"); 174 } 175 176 componentActions = new ComponentActionSink(logger, clientDataEncoder); 177 178 // Here's the magic of environmentals ... we can create a wrapper around 179 // the normal FormSupport environmental that intercepts some of the behavior. 180 // Here we're setting aside all the actions inside the FormFragment so that we 181 // can control whether those actions occur when the form is submitted. 182 183 FormSupport override = new FormSupportAdapter(formSupport) 184 { 185 @Override 186 public <T> void store(T component, ComponentAction<T> action) 187 { 188 componentActions.store(component, action); 189 } 190 191 @Override 192 public <T> void storeCancel(T component, ComponentAction<T> action) 193 { 194 componentActions.storeCancel(component, action); 195 } 196 197 @Override 198 public <T> void storeAndExecute(T component, ComponentAction<T> action) 199 { 200 componentActions.store(component, action); 201 202 action.execute(component); 203 } 204 }; 205 206 // Tada! Now all the enclosed components will use our override of FormSupport, 207 // until we pop it off. 208 209 environment.push(FormSupport.class, override); 210 211 } 212 213 /** 214 * Closes the <div> tag and pops off the {@link org.apache.tapestry5.services.FormSupport} environmental 215 * override. 216 * 217 * @param writer 218 */ 219 void afterRender(MarkupWriter writer) 220 { 221 Element hidden = hiddenFieldPositioner.getElement(); 222 223 hidden.attributes("type", "hidden", 224 225 "name", Form.FORM_DATA, 226 227 228 "value", componentActions.getClientData()); 229 230 if (!alwaysSubmit) 231 { 232 // Make it possible for the FormFragment to locate the hidden field, even if 233 // FormFragments get nested in some complex way. When the always submit option 234 // is enabled, there's no need for the hidden field to be locatable. 235 hidden.attributes("data-for-fragment", clientId); 236 } 237 238 writer.end(); // div 239 240 241 environment.pop(FormSupport.class); 242 } 243 244 public String getClientId() 245 { 246 return clientId; 247 } 248}