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    }