    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 &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    }