001    // Copyright 2006, 2007, 2008 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.base;
016    
017    import org.apache.tapestry5.*;
018    import org.apache.tapestry5.annotations.*;
019    import org.apache.tapestry5.corelib.internal.InternalMessages;
020    import org.apache.tapestry5.corelib.mixins.DiscardBody;
021    import org.apache.tapestry5.corelib.mixins.RenderDisabled;
022    import org.apache.tapestry5.corelib.mixins.RenderInformals;
023    import org.apache.tapestry5.ioc.annotations.Inject;
024    import org.apache.tapestry5.services.ComponentDefaultProvider;
025    import org.apache.tapestry5.services.FormSupport;
026    
027    import java.io.Serializable;
028    
029    /**
030     * Provides initialization of the clientId and elementName properties. In addition, adds the {@link RenderInformals},
031     * {@link RenderDisabled} and {@link DiscardBody} mixins.
032     */
033    @SupportsInformalParameters
034    public abstract class AbstractField implements Field
035    {
036        /**
037         * The user presentable label for the field. If not provided, a reasonable label is generated from the component's
038         * id, first by looking for a message key named "id-label" (substituting the component's actual id), then by
039         * converting the actual id to a presentable string (for example, "userId" to "User Id").
040         */
041        @Parameter(defaultPrefix = BindingConstants.LITERAL)
042        private String label;
043    
044        /**
045         * If true, then the field will render out with a disabled attribute (to turn off client-side behavior). Further, a
046         * disabled field ignores any value in the request when the form is submitted.
047         */
048        @Parameter("false")
049        private boolean disabled;
050    
051        @SuppressWarnings("unused")
052        @Mixin
053        private DiscardBody discardBody;
054    
055        @Environmental
056        private ValidationDecorator decorator;
057    
058    
059        static class Setup implements ComponentAction<AbstractField>, Serializable
060        {
061            private static final long serialVersionUID = 2690270808212097020L;
062    
063            private final String controlName;
064    
065            public Setup(String controlName)
066            {
067                this.controlName = controlName;
068            }
069    
070            public void execute(AbstractField component)
071            {
072                component.setupControlName(controlName);
073            }
074    
075            @Override
076            public String toString()
077            {
078                return String.format("AbstractField.Setup[%s]", controlName);
079            }
080        }
081    
082        static class ProcessSubmission implements ComponentAction<AbstractField>, Serializable
083        {
084            private static final long serialVersionUID = -4346426414137434418L;
085    
086            public void execute(AbstractField component)
087            {
088                component.processSubmission();
089            }
090    
091            @Override
092            public String toString()
093            {
094                return "AbstractField.ProcessSubmission";
095            }
096        }
097    
098        /**
099         * Used a shared instance for all types of fields, for efficiency.
100         */
101        private static final ProcessSubmission PROCESS_SUBMISSION_ACTION = new ProcessSubmission();
102    
103        /**
104         * The id used to generate a page-unique client-side identifier for the component. If a component renders multiple
105         * times, a suffix will be appended to the to id to ensure uniqueness. The uniqued value may be accessed via the
106         * {@link #getClientId() clientId property}.
107         */
108        @Parameter(value = "prop:componentResources.id", defaultPrefix = BindingConstants.LITERAL)
109        private String clientId;
110    
111        private String assignedClientId;
112    
113        private String controlName;
114    
115        @Environmental(false)
116        private FormSupport formSupport;
117    
118        @Environmental
119        private RenderSupport renderSupport;
120    
121        @Inject
122        private ComponentResources resources;
123    
124        @Inject
125        private ComponentDefaultProvider defaultProvider;
126    
127        final String defaultLabel()
128        {
129            return defaultProvider.defaultLabel(resources);
130        }
131    
132        public final String getLabel()
133        {
134            return label;
135        }
136    
137        @SetupRender
138        final void setup()
139        {
140            // By default, use the component id as the (base) client id. If the clientid
141            // parameter is bound, then that is the value to use.
142    
143            String id = clientId;
144    
145            // Often, these controlName and clientId will end up as the same value. There are many
146            // exceptions, including a form that renders inside a loop, or a form inside a component
147            // that is used multiple times.
148    
149            if (formSupport == null) throw new RuntimeException(InternalMessages.formFieldOutsideForm(getLabel()));
150    
151            assignedClientId = renderSupport.allocateClientId(id);
152            String controlName = formSupport.allocateControlName(id);
153    
154            formSupport.storeAndExecute(this, new Setup(controlName));
155            formSupport.store(this, PROCESS_SUBMISSION_ACTION);
156        }
157    
158        public final String getClientId()
159        {
160            return assignedClientId;
161        }
162    
163        public final String getControlName()
164        {
165            return controlName;
166        }
167    
168        public final boolean isDisabled()
169        {
170            return disabled;
171        }
172    
173        /**
174         * Invoked from within a ComponentCommand callback, to restore the component's elementName.
175         */
176        private void setupControlName(String controlName)
177        {
178            this.controlName = controlName;
179        }
180    
181        private void processSubmission()
182        {
183            if (!disabled) processSubmission(controlName);
184        }
185    
186        /**
187         * Used by subclasses to create a default binding to a property of the container matching the component id.
188         *
189         * @return a binding to the property, or null if the container does not have a corresponding property
190         */
191        protected final Binding createDefaultParameterBinding(String parameterName)
192        {
193            return defaultProvider.defaultBinding(parameterName, resources);
194        }
195    
196        /**
197         * Method implemented by subclasses to actually do the work of processing the submission of the form. The element's
198         * elementName property will already have been set. This method is only invoked if the field is <strong>not {@link
199         * #isDisabled() disabled}</strong>.
200         *
201         * @param elementName the name of the element (used to find the correct parameter in the request)
202         */
203        protected abstract void processSubmission(String elementName);
204    
205        /**
206         * Allows the validation decorator to write markup before the field itself writes markup.
207         */
208        @BeginRender
209        final void beforeDecorator()
210        {
211            decorator.beforeField(this);
212        }
213    
214        /**
215         * Allows the validation decorator to write markup after the field has written all of its markup.
216         */
217        @AfterRender
218        final void afterDecorator()
219        {
220            decorator.afterField(this);
221        }
222    
223        /**
224         * Invoked from subclasses after they have written their tag and (where appropriate) their informal parameters
225         * <em>and</em> have allowed their {@link Validator} to write markup as well.
226         */
227        protected final void decorateInsideField()
228        {
229            decorator.insideField(this);
230        }
231    
232        protected final void setDecorator(ValidationDecorator decorator)
233        {
234            this.decorator = decorator;
235        }
236    
237        protected final void setFormSupport(FormSupport formSupport)
238        {
239            this.formSupport = formSupport;
240        }
241    
242        /**
243         * Returns false; most components do not support declarative validation.
244         */
245        public boolean isRequired()
246        {
247            return false;
248        }
249    }