View Javadoc

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 }