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 &lt;div&gt; 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 &lt;div&gt; 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}