1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.myfaces.custom.subform;
20
21 import java.util.Iterator;
22 import java.util.List;
23
24 import javax.faces.component.EditableValueHolder;
25 import javax.faces.component.NamingContainer;
26 import javax.faces.component.UIComponent;
27 import javax.faces.component.UIComponentBase;
28 import javax.faces.context.FacesContext;
29 import javax.faces.event.ActionEvent;
30 import javax.faces.event.FacesEvent;
31 import javax.faces.event.PhaseId;
32
33 import org.apache.myfaces.shared_tomahawk.renderkit.RendererUtils;
34
35 /**
36 * A SubForm which will allow for partial validation
37 * and model update.
38 * <p>
39 * A subform to an existing form. Inputs in this form will only be
40 * validated and updated, if a t:commandButton or t:commandLink
41 * has been clicked with an actionFor attribute which references
42 * the client-id of this subform. Optionally, the validation will
43 * trigger if a commandButton or commandLink embedded in this
44 * subform has been clicked, except if this command is a
45 * t:commandButton or t:commandLink with an actionFor attribute
46 * which doesn't reference the client-id of this subform.
47 * </p>
48 * <p>
49 * Components will be validated and updated only if
50 * either a child-component of this form caused
51 * the submit of the form, or an extended commandLink
52 * or commandButton with the actionFor attribute set
53 * to the client-id of this component was used.
54 * </p>
55 * <p>
56 * You can have several comma-separated entries in
57 * the actionFor-attribute - with this it's possible to
58 * validate and update more than one subForm at once.
59 * </p>
60 *
61 * @JSFComponent
62 * name = "t:subform"
63 * class = "org.apache.myfaces.custom.subform.SubForm"
64 * tagClass = "org.apache.myfaces.custom.subform.SubFormTag"
65 * implements = "javax.faces.component.NamingContainer"
66 * @since 1.1.7
67 * @author Gerald Muellan
68 * @author Martin Marinschek
69 * Date: 19.01.2006
70 * Time: 13:58:18
71 */
72 public abstract class AbstractSubForm extends UIComponentBase
73 implements NamingContainer
74 {
75
76 public static final String COMPONENT_TYPE = "org.apache.myfaces.SubForm";
77 public static final String DEFAULT_RENDERER_TYPE = "org.apache.myfaces.SubForm";
78 public static final String COMPONENT_FAMILY = "org.apache.myfaces.SubForm";
79
80 private static final String PARTIAL_ENABLED = "org.apache.myfaces.IsPartialPhaseExecutionEnabled";
81 private boolean _submitted;
82
83 public AbstractSubForm()
84 {
85 super.setRendererType(DEFAULT_RENDERER_TYPE);
86 }
87
88 public String getFamily()
89 {
90 return COMPONENT_FAMILY;
91 }
92
93 public boolean isSubmitted()
94 {
95 return _submitted;
96 }
97
98 public void setSubmitted(boolean submitted)
99 {
100 _submitted = submitted;
101 }
102
103 /**
104 * true|false - set to false if you submit other subforms and would like to
105 * have your subform reflecting any model update. Default: true
106 *
107 * @JSFProperty
108 * @return
109 */
110 public abstract Boolean getPreserveSubmittedValues();
111
112
113 public void processDecodes(FacesContext context)
114 {
115 super.processDecodes(context);
116 if (!isRendered()) return;
117
118 if (!_submitted && Boolean.FALSE.equals(getPreserveSubmittedValues()))
119 {
120 // didn't find any better way as we do not know if we are submitted before the
121 // decode phase, but then all the other components have the submitted value
122 // set already.
123 // so lets reset them again ... boring hack.
124 resetSubmittedValues(this, context);
125 }
126 }
127
128 public void processValidators(FacesContext context)
129 {
130 if (context == null) throw new NullPointerException("context");
131 if (!isRendered()) return;
132
133 boolean partialEnabled = isPartialEnabled(context, PhaseId.PROCESS_VALIDATIONS);
134
135 if(partialEnabled || (_submitted && isEmptyList(context)))
136 {
137 for (Iterator it = getFacetsAndChildren(); it.hasNext(); )
138 {
139 UIComponent childOrFacet = (UIComponent)it.next();
140 childOrFacet.processValidators(context);
141 }
142 }
143 else
144 {
145 processSubFormValidators(this,context);
146 }
147 }
148
149 public void processUpdates(FacesContext context)
150 {
151 if (context == null) throw new NullPointerException("context");
152 if (!isRendered()) return;
153
154 boolean partialEnabled = isPartialEnabled(context,PhaseId.UPDATE_MODEL_VALUES);
155
156 if(partialEnabled || _submitted)
157 {
158 for (Iterator it = getFacetsAndChildren(); it.hasNext(); )
159 {
160 UIComponent childOrFacet = (UIComponent)it.next();
161 childOrFacet.processUpdates(context);
162 }
163 }
164 else
165 {
166 processSubFormUpdates(this,context);
167 }
168 }
169
170 private static void resetSubmittedValues(UIComponent comp, FacesContext context)
171 {
172 for (Iterator it = comp.getFacetsAndChildren(); it.hasNext(); )
173 {
174 UIComponent childOrFacet = (UIComponent)it.next();
175 if (childOrFacet instanceof AbstractSubForm)
176 {
177 // we are not responsible for this subForm, are we?
178 continue;
179 }
180
181 if (childOrFacet instanceof EditableValueHolder)
182 {
183 ((EditableValueHolder) childOrFacet).setSubmittedValue(null);
184 }
185
186 resetSubmittedValues(childOrFacet, context);
187 }
188 }
189
190 private static void processSubFormUpdates(UIComponent comp, FacesContext context)
191 {
192 for (Iterator it = comp.getFacetsAndChildren(); it.hasNext(); )
193 {
194 UIComponent childOrFacet = (UIComponent)it.next();
195
196 if(childOrFacet instanceof AbstractSubForm)
197 {
198 childOrFacet.processUpdates(context);
199 }
200 else
201 {
202 processSubFormUpdates(childOrFacet, context);
203 }
204 }
205 }
206
207 private static void processSubFormValidators(UIComponent comp, FacesContext context)
208 {
209 for (Iterator it = comp.getFacetsAndChildren(); it.hasNext(); )
210 {
211 UIComponent childOrFacet = (UIComponent)it.next();
212
213 if(childOrFacet instanceof AbstractSubForm)
214 {
215 childOrFacet.processValidators(context);
216 }
217 else
218 {
219 processSubFormValidators(childOrFacet, context);
220 }
221 }
222 }
223
224 public void queueEvent(FacesEvent event)
225 {
226 if(event instanceof ActionEvent)
227 {
228 _submitted = true;
229 }
230
231 // This idea is taken from ADF faces - my approach of checking for instanceof ActionEvent
232 // didn't go as far as necessary for dataTables.
233 // In the dataTable case, the ActionEvent is wrapped in an EventWrapper
234 //
235 // I still believe the second part of the if condition is a hack:
236 // If the event is being queued for anything *after* APPLY_REQUEST_VALUES,
237 // then this subform is active - IMHO there might be other events not relating
238 // to the action system which are queued after this phase.
239 if (PhaseId.APPLY_REQUEST_VALUES.compareTo(event.getPhaseId()) < 0)
240 {
241 setSubmitted(true);
242 }
243
244 super.queueEvent(event);
245 }
246
247 protected boolean isEmptyList(FacesContext context)
248 {
249 //get the list of (parent) client-ids for which a validation/model update should be performed
250 List li = (List) context.getExternalContext().getRequestMap().get(
251 RendererUtils.ACTION_FOR_LIST);
252
253 return li==null || li.size()==0;
254 }
255
256 /**Sets up information if this component is included in
257 * the group of components which are associated with the current action.
258 *
259 * @param context
260 * @return true if there has been a change by this setup which has to be undone after the phase finishes.
261 */
262 protected boolean isPartialEnabled(FacesContext context, PhaseId phaseId)
263 {
264 //we want to execute validation (and model update) only
265 //if certain conditions are met
266 //especially, we want to switch validation/update on/off depending on
267 //the attribute "actionFor" of a MyFaces extended button or link
268 //if you use commandButtons which don't set these
269 //request parameters, this won't cause any adverse effects
270
271 boolean partialEnabled = false;
272
273 //get the list of (parent) client-ids for which a validation/model update should be performed
274 List li = (List) context.getExternalContext().getRequestMap().get(
275 RendererUtils.ACTION_FOR_LIST);
276
277 //if there is a list, check if the current client id
278 //matches an entry of the list
279 if(li != null && li.contains(getClientId(context)))
280 {
281 if(!context.getExternalContext().getRequestMap().containsKey(PARTIAL_ENABLED))
282 {
283 partialEnabled=true;
284 }
285 }
286
287 if(partialEnabled)
288 {
289 //get the list of phases which should be executed
290 List phaseList = (List) context.getExternalContext().getRequestMap().get(
291 RendererUtils.ACTION_FOR_PHASE_LIST);
292
293 if(phaseList != null && !phaseList.isEmpty() && !phaseList.contains(phaseId))
294 {
295 partialEnabled=false;
296 }
297 }
298
299 return partialEnabled;
300 }
301
302
303 }