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 }