"Fossies" - the Fresh Open Source Software Archive

Member "apidocs/src-html/org/apache/tapestry5/corelib/base/AbstractField.html" (9 Sep 2020, 34073 Bytes) of package /linux/www/apache-tapestry-5.6.1-apidocs.zip:


Caution: In this restricted "Fossies" environment the current HTML page may not be correctly presentated and may have some non-functional links. You can here alternatively try to browse the pure source code or just view or download the uninterpreted raw source code. If the rendering is insufficient you may try to find and view the page on the project site itself.

001// Licensed under the Apache License, Version 2.0 (the "License");
002// you may not use this file except in compliance with the License.
003// You may obtain a copy of the License at
004//
005// http://www.apache.org/licenses/LICENSE-2.0
006//
007// Unless required by applicable law or agreed to in writing, software
008// distributed under the License is distributed on an "AS IS" BASIS,
009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
010// See the License for the specific language governing permissions and
011// limitations under the License.
012
013package org.apache.tapestry5.corelib.base;
014
015import org.apache.tapestry5.*;
016import org.apache.tapestry5.annotations.*;
017import org.apache.tapestry5.corelib.mixins.DiscardBody;
018import org.apache.tapestry5.corelib.mixins.RenderInformals;
019import org.apache.tapestry5.internal.BeanValidationContext;
020import org.apache.tapestry5.internal.InternalComponentResources;
021import org.apache.tapestry5.internal.services.FormControlNameManager;
022import org.apache.tapestry5.ioc.annotations.Inject;
023import org.apache.tapestry5.ioc.annotations.Symbol;
024import org.apache.tapestry5.ioc.internal.util.InternalUtils;
025import org.apache.tapestry5.ioc.internal.util.TapestryException;
026import org.apache.tapestry5.services.ComponentDefaultProvider;
027import org.apache.tapestry5.services.Environment;
028import org.apache.tapestry5.services.FormSupport;
029import org.apache.tapestry5.services.Request;
030import org.apache.tapestry5.services.javascript.JavaScriptSupport;
031
032import java.io.Serializable;
033
034/**
035 * Provides initialization of the clientId and elementName properties. In addition, adds the {@link RenderInformals},
036 * and {@link DiscardBody} mixins.
037 *
038 * @tapestrydoc
039 */
040@SupportsInformalParameters
041public abstract class AbstractField implements Field
042{
043    /**
044     * The user presentable label for the field. If not provided, a reasonable label is generated from the component's
045     * id, first by looking for a message key named "id-label" (substituting the component's actual id), then by
046     * converting the actual id to a presentable string (for example, "userId" to "User Id").
047     */
048    @Parameter(defaultPrefix = BindingConstants.LITERAL)
049    protected String label;
050
051    /**
052     * If true, then the field will render out with a disabled attribute
053     * (to turn off client-side behavior). When the form is submitted, the
054     * bound value is evaluated again and, if true, the field's value is
055     * ignored (not even validated) and the component's events are not fired.
056     */
057    @Parameter("false")
058    protected boolean disabled;
059
060    @SuppressWarnings("unused")
061    @Mixin
062    private DiscardBody discardBody;
063
064    @Environmental
065    protected ValidationDecorator decorator;
066
067    @Inject
068    protected Environment environment;
069
070    @Inject
071    @Symbol(SymbolConstants.FORM_FIELD_CSS_CLASS)
072    protected String cssClass;
073
074    static class Setup implements ComponentAction<AbstractField>, Serializable
075    {
076        private static final long serialVersionUID = 2690270808212097020L;
077
078        private final String controlName;
079
080        public Setup(String controlName)
081        {
082            this.controlName = controlName;
083        }
084
085        public void execute(AbstractField component)
086        {
087            component.setupControlName(controlName);
088        }
089
090        @Override
091        public String toString()
092        {
093            return String.format("AbstractField.Setup[%s]", controlName);
094        }
095    }
096
097    static class ProcessSubmission implements ComponentAction<AbstractField>, Serializable
098    {
099        private static final long serialVersionUID = -4346426414137434418L;
100
101        public void execute(AbstractField component)
102        {
103            component.processSubmission();
104        }
105
106        @Override
107        public String toString()
108        {
109            return "AbstractField.ProcessSubmission";
110        }
111    }
112
113    /**
114     * Used a shared instance for all types of fields, for efficiency.
115     */
116    private static final ProcessSubmission PROCESS_SUBMISSION_ACTION = new ProcessSubmission();
117
118    /**
119     * Used to explicitly set the client-side id of the element for this component. Normally this is not
120     * bound (or null) and {@link org.apache.tapestry5.services.javascript.JavaScriptSupport#allocateClientId(org.apache.tapestry5.ComponentResources)}
121     * is used to generate a unique client-id based on the component's id. In some cases, when creating client-side
122     * behaviors, it is useful to explicitly set a unique id for an element using this parameter.
123     * 
124     * Certain values, such as "submit", "method", "reset", etc., will cause client-side conflicts and are not allowed; using such will
125     * cause a runtime exception.
126     */
127    @Parameter(defaultPrefix = BindingConstants.LITERAL)
128    private String clientId;
129
130    /**
131     * A rarely used option that indicates that the actual client id should start with the clientId parameter (if non-null)
132     * but should still pass that Id through {@link org.apache.tapestry5.services.javascript.JavaScriptSupport#allocateClientId(String)}
133     * to generate the final id.
134     * 
135     * An example of this are the components used inside a {@link org.apache.tapestry5.corelib.components.BeanEditor} which
136     * will specify a clientId (based on the property name) but still require that it be unique.
137     * 
138     * Defaults to false.
139     *
140     * @since 5.4
141     */
142    @Parameter
143    private boolean ensureClientIdUnique;
144
145
146    private String assignedClientId;
147
148    private String controlName;
149
150    @Environmental(false)
151    protected FormSupport formSupport;
152
153    @Environmental
154    protected JavaScriptSupport javaScriptSupport;
155
156    @Environmental
157    protected ValidationTracker validationTracker;
158
159    @Inject
160    protected ComponentResources resources;
161
162    @Inject
163    protected ComponentDefaultProvider defaultProvider;
164
165    @Inject
166    protected Request request;
167
168    @Inject
169    protected FieldValidationSupport fieldValidationSupport;
170
171    @Inject
172    private FormControlNameManager formControlNameManager;
173
174    final String defaultLabel()
175    {
176        return defaultProvider.defaultLabel(resources);
177    }
178
179    public final String getLabel()
180    {
181        return label;
182    }
183
184    @SetupRender
185    final void setup()
186    {
187        // Often, these controlName and clientId will end up as the same value. There are many
188        // exceptions, including a form that renders inside a loop, or a form inside a component
189        // that is used multiple times.
190
191        if (formSupport == null)
192            throw new RuntimeException(String.format("Component %s must be enclosed by a Form component.",
193                    resources.getCompleteId()));
194
195        assignedClientId = allocateClientId();
196
197        String controlName = formSupport.allocateControlName(assignedClientId);
198
199        formSupport.storeAndExecute(this, new Setup(controlName));
200        formSupport.store(this, PROCESS_SUBMISSION_ACTION);
201    }
202
203    private String allocateClientId()
204    {
205        if (clientId == null)
206        {
207            return javaScriptSupport.allocateClientId(resources);
208        }
209
210
211        if (ensureClientIdUnique)
212        {
213            return javaScriptSupport.allocateClientId(clientId);
214        } else
215        {
216            // See https://issues.apache.org/jira/browse/TAP5-1632
217            // Basically, on the client, there can be a convenience lookup inside a HTMLFormElement
218            // by id OR name; so an id of "submit" (for example) will mask the HTMLFormElement.submit()
219            // function.
220
221            if (formControlNameManager.isReserved(clientId))
222            {
223                throw new TapestryException(String.format(
224                        "The value '%s' for parameter clientId is not allowed as it causes a naming conflict in the client-side DOM. " +
225                                "Select an id not in the list: %s.",
226                        clientId,
227                        InternalUtils.joinSorted(formControlNameManager.getReservedNames())), this, null);
228            }
229        }
230
231        return clientId;
232    }
233
234    public final String getClientId()
235    {
236        return assignedClientId;
237    }
238
239    public final String getControlName()
240    {
241        return controlName;
242    }
243
244    public final boolean isDisabled()
245    {
246        return disabled;
247    }
248
249    /**
250     * Invoked from within a ComponentCommand callback, to restore the component's elementName.
251     */
252    private void setupControlName(String controlName)
253    {
254        this.controlName = controlName;
255    }
256
257    private void processSubmission()
258    {
259        if (!disabled)
260            processSubmission(controlName);
261    }
262
263    /**
264     * Method implemented by subclasses to actually do the work of processing the submission of the form. The element's
265     * controlName property will already have been set. This method is only invoked if the field is <strong>not
266     * {@link #isDisabled() disabled}</strong>.
267     *
268     * @param controlName
269     *         the control name of the rendered element (used to find the correct parameter in the request)
270     */
271    protected abstract void processSubmission(String controlName);
272
273    /**
274     * Allows the validation decorator to write markup before the field itself writes markup.
275     */
276    @BeginRender
277    final void beforeDecorator()
278    {
279        decorator.beforeField(this);
280    }
281
282    /**
283     * Allows the validation decorator to write markup after the field has written all of its markup.
284     * In addition, may invoke the <code>core/fields:showValidationError</code> function to present
285     * the field's error (if it has one) to the user.
286     */
287    @AfterRender
288    final void afterDecorator()
289    {
290        decorator.afterField(this);
291
292        String error = validationTracker.getError(this);
293
294        if (error != null)
295        {
296            javaScriptSupport.require("t5/core/fields").invoke("showValidationError").with(assignedClientId, error);
297        }
298    }
299
300    /**
301     * Invoked from subclasses after they have written their tag and (where appropriate) their informal parameters
302     * <em>and</em> have allowed their {@link Validator} to write markup as well.
303     */
304    protected final void decorateInsideField()
305    {
306        decorator.insideField(this);
307    }
308
309    protected final void setDecorator(ValidationDecorator decorator)
310    {
311        this.decorator = decorator;
312    }
313
314    protected final void setFormSupport(FormSupport formSupport)
315    {
316        this.formSupport = formSupport;
317    }
318
319    /**
320     * Returns false; most components do not support declarative validation.
321     */
322    public boolean isRequired()
323    {
324        return false;
325    }
326
327    // This is set to true for some unit test.
328    private boolean beanValidationDisabled = false;
329
330    protected void putPropertyNameIntoBeanValidationContext(String parameterName)
331    {
332        if (beanValidationDisabled)
333        {
334            return;
335        }
336
337        String propertyName = ((InternalComponentResources) resources).getPropertyName(parameterName);
338
339        BeanValidationContext beanValidationContext = environment.peek(BeanValidationContext.class);
340
341        if (beanValidationContext == null)
342            return;
343
344        // If field is inside BeanEditForm, then property is already set
345        if (beanValidationContext.getCurrentProperty() == null)
346        {
347            beanValidationContext.setCurrentProperty(propertyName);
348        }
349    }
350
351    protected void removePropertyNameFromBeanValidationContext()
352    {
353        if (beanValidationDisabled)
354        {
355            return;
356        }
357
358        BeanValidationContext beanValidationContext = environment.peek(BeanValidationContext.class);
359
360        if (beanValidationContext == null)
361            return;
362
363        beanValidationContext.setCurrentProperty(null);
364    }
365}