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 }