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.datalist;
20  
21  import java.util.Iterator;
22  import java.util.Map;
23  
24  import javax.el.ValueExpression;
25  import javax.faces.FacesException;
26  import javax.faces.component.ContextCallback;
27  import javax.faces.component.UIComponent;
28  import javax.faces.component.UINamingContainer;
29  import javax.faces.context.FacesContext;
30  
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.myfaces.component.UserRoleAware;
34  
35  /**
36   * Similar to dataTable, but does not render a table. 
37   * 
38   * Instead the layout attribute controls how each dataRow is rendered. 
39   * 
40   * Unless otherwise specified, all attributes accept static values or EL expressions.
41   * 
42   * @JSFComponent
43   *   name = "t:dataList"
44   *   class = "org.apache.myfaces.custom.datalist.HtmlDataList"
45   *   tagClass = "org.apache.myfaces.custom.datalist.HtmlDataListTag"
46   * @since 1.1.7
47   * @author Manfred Geiler (latest modification by $Author: lu4242 $)
48   * @version $Revision: 940152 $ $Date: 2010-05-01 21:03:39 -0500 (Sáb, 01 May 2010) $
49   */
50  public abstract class AbstractHtmlDataList
51          extends org.apache.myfaces.component.html.ext.HtmlDataTableHack
52          implements UserRoleAware
53  {
54      public static final String COMPONENT_TYPE = "org.apache.myfaces.HtmlDataList";
55      private static final String DEFAULT_RENDERER_TYPE = "org.apache.myfaces.List";
56          
57      private static Log log = LogFactory.getLog(AbstractHtmlDataList.class);
58      private static final int PROCESS_DECODES = 1;
59      private static final int PROCESS_VALIDATORS = 2; // not currently in use
60      private static final int PROCESS_UPDATES = 3; // not currently in use
61      
62      private transient FacesContext _facesContext;
63  
64      /**
65       * Throws NullPointerException if context is null.  Sets row index to -1, 
66       * calls processChildren, sets row index to -1.
67       */
68  
69      public void processDecodes(FacesContext context)
70      {
71  
72          if (context == null)
73              throw new NullPointerException("context");
74          if (!isRendered())
75              return;
76  
77          setRowIndex(-1);
78          processChildren(context, PROCESS_DECODES);
79          setRowIndex(-1);
80          try
81          {
82              decode(context);
83          }
84          catch (RuntimeException e)
85          {
86              context.renderResponse();
87              throw e;
88          }        
89      }
90  
91      public void processUpdates(FacesContext context)
92      {
93          if (context == null)
94              throw new NullPointerException("context");
95          if (!isRendered())
96              return;
97  
98          setRowIndex(-1);
99          processChildren(context, PROCESS_UPDATES);
100         setRowIndex(-1);
101         checkUpdateModelError(context);
102     }
103 
104     public void processValidators(FacesContext context)
105     {
106         if (context == null)
107             throw new NullPointerException("context");
108         if (!isRendered())
109             return;
110 
111         setRowIndex(-1);
112         processChildren(context, PROCESS_VALIDATORS);
113         setRowIndex(-1);
114         checkUpdateModelError(context);        
115     }
116 
117     /**
118      * Iterates over all children, processes each according to the specified 
119      * process action if the child is rendered.
120      */
121 
122     public void processChildren(FacesContext context, int processAction)
123     {
124         // use this method for processing other than decode ?
125         int first = getFirst();
126         int rows = getRows();
127         int last = rows == 0 ? getRowCount() : first + rows;
128 
129         if (log.isTraceEnabled())
130         {
131             log.trace("processing " + getChildCount()
132                             + " children: starting at " + first
133                             + ", ending at " + last);
134         }
135 
136         for (int rowIndex = first; last == -1 || rowIndex < last; rowIndex++)
137         {
138 
139             setRowIndex(rowIndex);
140 
141             if (!isRowAvailable())
142             {
143                 if (log.isTraceEnabled())
144                 {
145                     log.trace("scrolled past the last row, aborting");
146                 }
147                 break;
148             }
149 
150             for (Iterator it = getChildren().iterator(); it.hasNext();)
151             {
152                 UIComponent child = (UIComponent) it.next();
153                 if (child.isRendered())
154                     process(context, child, processAction);
155             }
156         }
157     }
158 
159     /**
160      * Copy and pasted from UIData in order to maintain binary compatibility.
161      */
162 
163     private void process(FacesContext context, UIComponent component,
164             int processAction)
165     {
166         switch (processAction)
167         {
168         case PROCESS_DECODES:
169             component.processDecodes(context);
170             break;
171         case PROCESS_VALIDATORS:
172             component.processValidators(context);
173             break;
174         case PROCESS_UPDATES:
175             component.processUpdates(context);
176             break;
177         }
178     }
179     
180     public void setRowIndex(int rowIndex)
181     {
182         super.setRowIndex(rowIndex);
183         String rowIndexVar = getRowIndexVar();
184         String rowCountVar = getRowCountVar();
185         if (rowIndexVar != null || rowCountVar != null)
186         {
187             Map requestMap = FacesContext.getCurrentInstance().getExternalContext().getRequestMap();
188             if (rowIndex >= 0)
189             {
190                 //regular row index, update request scope variables
191                 if (rowIndexVar != null)
192                 {
193                     requestMap.put(getRowIndexVar(), new Integer(rowIndex));
194                 }
195                 if (rowCountVar != null)
196                 {
197                     requestMap.put(getRowCountVar(), new Integer(getRowCount()));
198                 }
199             }
200             else
201             {
202                 //rowIndex == -1 means end of loop --> remove request scope variables
203                 if (rowIndexVar != null)
204                 {
205                     requestMap.remove(getRowIndexVar());
206                 }
207                 if (rowCountVar != null)
208                 {
209                     requestMap.remove(getRowCountVar());
210                 }
211             }
212         }
213     }
214     
215     @Override
216     protected void restoreDescendantComponentStates(Object state)
217     {
218         restoreDescendantComponentStates(getChildren().iterator(), state, true);
219     }
220     
221     @Override
222     protected Object saveDescendantComponentStates()
223     {
224         return saveDescendantComponentStates(getChildren().iterator(), true);
225     }
226 
227     @Override
228     public boolean invokeOnComponent(FacesContext context, String clientId, ContextCallback callback)
229         throws FacesException
230     {
231         if (context == null || clientId == null || callback == null)
232         {
233             throw new NullPointerException();
234         }
235         
236         final String baseClientId = getClientId(context);
237 
238         // searching for this component?
239         boolean returnValue = baseClientId.equals(clientId);
240 
241         boolean isCachedFacesContext = isTemporalFacesContext();
242         if (!isCachedFacesContext)
243         {
244             setTemporalFacesContext(context);
245         }
246         
247         try
248         {
249             if (returnValue)
250             {
251                 try
252                 {
253                     callback.invokeContextCallback(context, this);
254                     return true;
255                 }
256                 catch (Exception e)
257                 {
258                     throw new FacesException(e);
259                 }
260             }
261     
262             // Now Look throught facets on this UIComponent
263             for (Iterator<UIComponent> it = this.getFacets().values().iterator(); !returnValue && it.hasNext();)
264             {
265                 returnValue = it.next().invokeOnComponent(context, clientId, callback);
266             }
267     
268             if (returnValue)
269             {
270                 return returnValue;
271             }
272             
273             // is the component an inner component?
274             if (clientId.startsWith(baseClientId))
275             {
276                 // Check if the clientId for the component, which we 
277                 // are looking for, has a rowIndex attached
278                 char separator = UINamingContainer.SEPARATOR_CHAR;
279                 ValueExpression rowKeyVE = getValueExpression("rowKey");
280                 boolean rowKeyFound = false;
281                 
282                 if (rowKeyVE != null)
283                 {
284                     int oldRow = this.getRowIndex();
285                     try
286                     {
287                         // iterate over the rows
288                         int rowsToProcess = getRows();
289                         // if getRows() returns 0, all rows have to be processed
290                         if (rowsToProcess == 0)
291                         {
292                             rowsToProcess = getRowCount();
293                         }
294                         int rowIndex = getFirst();
295                         for (int rowsProcessed = 0; rowsProcessed < rowsToProcess; rowsProcessed++, rowIndex++)
296                         {
297                             setRowIndex(rowIndex);
298                             if (!isRowAvailable())
299                             {
300                                 break;
301                             }
302                             
303                             if (clientId.startsWith(getContainerClientId(context)))
304                             {
305                                 rowKeyFound = true;
306                                 break;
307                             }
308                         }
309                         
310                         if (rowKeyFound)
311                         {
312                             for (Iterator<UIComponent> it1 = getChildren().iterator(); 
313                                     !returnValue && it1.hasNext();)
314                             {
315                                 //recursive call to find the component
316                                 returnValue = it1.next().invokeOnComponent(context, clientId, callback);
317                             }
318                         }
319                     }
320                     finally
321                     {
322                         this.setRowIndex(oldRow);
323                     }
324                 }
325                 //If the char next to baseClientId is the separator one and
326                 //the subId matches the regular expression
327                 if (rowKeyVE == null && clientId.matches(baseClientId + separator+"[0-9]+"+separator+".*"))
328                 {
329                     String subId = clientId.substring(baseClientId.length() + 1);
330                     String clientRow = subId.substring(0, subId.indexOf(separator));
331         
332                     //Now we save the current position
333                     int oldRow = this.getRowIndex();
334                     
335                     // try-finally --> make sure, that the old row index is restored
336                     try
337                     {
338                         //The conversion is safe, because its already checked on the
339                         //regular expresion
340                         this.setRowIndex(Integer.parseInt(clientRow));
341                         
342                         // check, if the row is available
343                         if (!isRowAvailable())
344                         {
345                             return false;
346                         }
347             
348                         for (Iterator<UIComponent> it1 = getChildren().iterator(); 
349                                 !returnValue && it1.hasNext();)
350                         {
351                             //recursive call to find the component
352                             returnValue = it1.next().invokeOnComponent(context, clientId, callback);
353                         }
354                     }
355                     finally
356                     {
357                         //Restore the old position. Doing this prevent
358                         //side effects.
359                         this.setRowIndex(oldRow);
360                     }
361                 }
362             }
363         }
364         finally
365         {
366             if (!isCachedFacesContext)
367             {
368                 setTemporalFacesContext(null);
369             }
370         }
371 
372         return returnValue;
373     }
374     
375     @Override
376     protected FacesContext getFacesContext()
377     {
378         if (_facesContext == null)
379         {
380             return super.getFacesContext();
381         }
382         else
383         {
384             return _facesContext;
385         }
386     }
387     
388     private boolean isTemporalFacesContext()
389     {
390         return _facesContext != null;
391     }
392     
393     private void setTemporalFacesContext(FacesContext facesContext)
394     {
395         _facesContext = facesContext;
396     }
397 
398     /**
399      * A parameter name, under which the rowCount is set in request 
400      * scope similar to the var parameter.
401      * 
402      * @JSFProperty
403      */
404     public abstract String getRowCountVar();
405     
406     /**
407      *  A parameter name, under which the current rowIndex is set in 
408      *  request scope similar to the var parameter.
409      * 
410      * @JSFProperty
411      */
412     public abstract String getRowIndexVar();
413 
414     /**
415      * simple|unorderedList|orderedList
416      * <ul>
417      *   <li>simple = for each dataRow all children are simply rendered</li>
418      *   <li>unorderedList = the list is rendered as HTML unordered list (= bullet list)</li>
419      *   <li>orderedList = the list is rendered as HTML ordered list</li>
420      * </ul>
421      * Default: simple
422      * 
423      * @JSFProperty
424      */
425     public abstract String getLayout();
426     
427     /**
428      * CSS class to be applied to individual items in the list
429      * 
430      * @JSFProperty
431      */
432     public abstract String getItemStyleClass();
433     
434     /**
435      * OnClick handler to be applied to individual items in the list
436      * 
437      * @JSFProperty
438      */
439     public abstract String getItemOnClick();
440 }