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
015package org.apache.tapestry5.corelib.components;
016
017import org.apache.tapestry5.BindingConstants;
018import org.apache.tapestry5.CSSClassConstants;
019import org.apache.tapestry5.ClientElement;
020import org.apache.tapestry5.ComponentAction;
021import org.apache.tapestry5.ComponentResources;
022import org.apache.tapestry5.MarkupWriter;
023import org.apache.tapestry5.annotations.Environmental;
024import org.apache.tapestry5.annotations.Parameter;
025import org.apache.tapestry5.annotations.SupportsInformalParameters;
026import org.apache.tapestry5.corelib.internal.ComponentActionSink;
027import org.apache.tapestry5.corelib.internal.FormSupportAdapter;
028import org.apache.tapestry5.corelib.internal.HiddenFieldPositioner;
029import org.apache.tapestry5.corelib.mixins.TriggerFragment;
030import org.apache.tapestry5.dom.Element;
031import org.apache.tapestry5.ioc.annotations.Inject;
032import org.apache.tapestry5.services.ClientBehaviorSupport;
033import org.apache.tapestry5.services.ClientDataEncoder;
034import org.apache.tapestry5.services.Environment;
035import org.apache.tapestry5.services.FormSupport;
036import org.apache.tapestry5.services.HiddenFieldLocationRules;
037import org.apache.tapestry5.services.javascript.JavaScriptSupport;
038import 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
063public 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 &lt;div&gt; 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 &lt;div&gt; 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}