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.Collection;
22  import java.util.Iterator;
23  import java.util.Map;
24  
25  import javax.el.ValueExpression;
26  import javax.faces.FacesException;
27  import javax.faces.component.ContextCallback;
28  import javax.faces.component.UIComponent;
29  import javax.faces.component.UINamingContainer;
30  import javax.faces.component.visit.VisitCallback;
31  import javax.faces.component.visit.VisitContext;
32  import javax.faces.component.visit.VisitResult;
33  import javax.faces.context.FacesContext;
34  
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  import org.apache.myfaces.component.UserRoleAware;
38  
39  /**
40   * Similar to dataTable, but does not render a table. 
41   * 
42   * Instead the layout attribute controls how each dataRow is rendered. 
43   * 
44   * Unless otherwise specified, all attributes accept static values or EL expressions.
45   * 
46   * @JSFComponent
47   *   name = "t:dataList"
48   *   class = "org.apache.myfaces.custom.datalist.HtmlDataList"
49   *   tagClass = "org.apache.myfaces.custom.datalist.HtmlDataListTag"
50   * @since 1.1.7
51   * @author Manfred Geiler (latest modification by $Author: lu4242 $)
52   * @version $Revision: 940152 $ $Date: 2010-05-01 21:03:39 -0500 (Sáb, 01 May 2010) $
53   */
54  public abstract class AbstractHtmlDataList
55          extends org.apache.myfaces.component.html.ext.HtmlDataTableHack
56          implements UserRoleAware
57  {
58      public static final String COMPONENT_TYPE = "org.apache.myfaces.HtmlDataList";
59      private static final String DEFAULT_RENDERER_TYPE = "org.apache.myfaces.List";
60          
61      private static Log log = LogFactory.getLog(AbstractHtmlDataList.class);
62      private static final int PROCESS_DECODES = 1;
63      private static final int PROCESS_VALIDATORS = 2; // not currently in use
64      private static final int PROCESS_UPDATES = 3; // not currently in use
65      private static final String SKIP_ITERATION_HINT = "javax.faces.visit.SKIP_ITERATION";
66      
67      private transient FacesContext _facesContext;
68  
69      /**
70       * Throws NullPointerException if context is null.  Sets row index to -1, 
71       * calls processChildren, sets row index to -1.
72       */
73  
74      public void processDecodes(FacesContext context)
75      {
76  
77          if (context == null)
78              throw new NullPointerException("context");
79          if (!isRendered())
80              return;
81  
82          setRowIndex(-1);
83          processChildren(context, PROCESS_DECODES);
84          setRowIndex(-1);
85          try
86          {
87              decode(context);
88          }
89          catch (RuntimeException e)
90          {
91              context.renderResponse();
92              throw e;
93          }        
94      }
95  
96      public void processUpdates(FacesContext context)
97      {
98          if (context == null)
99              throw new NullPointerException("context");
100         if (!isRendered())
101             return;
102 
103         setRowIndex(-1);
104         processChildren(context, PROCESS_UPDATES);
105         setRowIndex(-1);
106         checkUpdateModelError(context);
107     }
108 
109     public void processValidators(FacesContext context)
110     {
111         if (context == null)
112             throw new NullPointerException("context");
113         if (!isRendered())
114             return;
115 
116         setRowIndex(-1);
117         processChildren(context, PROCESS_VALIDATORS);
118         setRowIndex(-1);
119         checkUpdateModelError(context);        
120     }
121 
122     /**
123      * Iterates over all children, processes each according to the specified 
124      * process action if the child is rendered.
125      */
126 
127     public void processChildren(FacesContext context, int processAction)
128     {
129         // use this method for processing other than decode ?
130         int first = getFirst();
131         int rows = getRows();
132         int last = rows == 0 ? getRowCount() : first + rows;
133 
134         if (log.isTraceEnabled())
135         {
136             log.trace("processing " + getChildCount()
137                             + " children: starting at " + first
138                             + ", ending at " + last);
139         }
140 
141         for (int rowIndex = first; last == -1 || rowIndex < last; rowIndex++)
142         {
143 
144             setRowIndex(rowIndex);
145 
146             if (!isRowAvailable())
147             {
148                 if (log.isTraceEnabled())
149                 {
150                     log.trace("scrolled past the last row, aborting");
151                 }
152                 break;
153             }
154 
155             for (Iterator it = getChildren().iterator(); it.hasNext();)
156             {
157                 UIComponent child = (UIComponent) it.next();
158                 if (child.isRendered())
159                     process(context, child, processAction);
160             }
161         }
162     }
163 
164     /**
165      * Copy and pasted from UIData in order to maintain binary compatibility.
166      */
167 
168     private void process(FacesContext context, UIComponent component,
169             int processAction)
170     {
171         switch (processAction)
172         {
173         case PROCESS_DECODES:
174             component.processDecodes(context);
175             break;
176         case PROCESS_VALIDATORS:
177             component.processValidators(context);
178             break;
179         case PROCESS_UPDATES:
180             component.processUpdates(context);
181             break;
182         }
183     }
184     
185     public void setRowIndex(int rowIndex)
186     {
187         super.setRowIndex(rowIndex);
188         String rowIndexVar = getRowIndexVar();
189         String rowCountVar = getRowCountVar();
190         if (rowIndexVar != null || rowCountVar != null)
191         {
192             Map requestMap = FacesContext.getCurrentInstance().getExternalContext().getRequestMap();
193             if (rowIndex >= 0)
194             {
195                 //regular row index, update request scope variables
196                 if (rowIndexVar != null)
197                 {
198                     requestMap.put(getRowIndexVar(), new Integer(rowIndex));
199                 }
200                 if (rowCountVar != null)
201                 {
202                     requestMap.put(getRowCountVar(), new Integer(getRowCount()));
203                 }
204             }
205             else
206             {
207                 //rowIndex == -1 means end of loop --> remove request scope variables
208                 if (rowIndexVar != null)
209                 {
210                     requestMap.remove(getRowIndexVar());
211                 }
212                 if (rowCountVar != null)
213                 {
214                     requestMap.remove(getRowCountVar());
215                 }
216             }
217         }
218     }
219     
220     @Override
221     protected void restoreDescendantComponentStates(Object state)
222     {
223         restoreDescendantComponentStates(getChildren().iterator(), state, true);
224     }
225 
226     @Override
227     protected Object saveDescendantComponentStates()
228     {
229         return saveDescendantComponentStates(getChildren().iterator(), true);
230     }
231     
232     @Override
233     protected Map<String,Object> saveFullDescendantComponentStates(FacesContext facesContext)
234     {
235         return saveFullDescendantComponentStates(facesContext, null, getChildren().iterator(), true, getContainerClientId(facesContext));
236     }
237     
238     @Override
239     protected void restoreFullDescendantComponentStates(FacesContext facesContext, Object initialState)
240     {
241         restoreFullDescendantComponentStates(facesContext, getChildren().iterator(), initialState, true);
242     }
243 
244     @Override
245     protected void restoreFullDescendantComponentDeltaStates(FacesContext facesContext,
246             Map<String, Object> rowState, Object initialState)
247     {
248         restoreFullDescendantComponentDeltaStates(facesContext, getChildren().iterator(), rowState, initialState, true, getContainerClientId(facesContext));
249     }
250     
251     @Override
252     protected Collection<Object[]> saveDescendantInitialComponentStates(FacesContext facesContext)
253     {
254         return saveDescendantInitialComponentStates(facesContext, getChildren().iterator(), true);
255     }
256 
257     @Override
258     public boolean invokeOnComponent(FacesContext context, String clientId, ContextCallback callback)
259         throws FacesException
260     {
261         if (context == null || clientId == null || callback == null)
262         {
263             throw new NullPointerException();
264         }
265         
266         final String baseClientId = getClientId(context);
267 
268         // searching for this component?
269         boolean returnValue = baseClientId.equals(clientId);
270 
271         boolean isCachedFacesContext = isTemporalFacesContext();
272         if (!isCachedFacesContext)
273         {
274             setTemporalFacesContext(context);
275         }
276         
277         pushComponentToEL(context, this);
278         try
279         {
280             if (returnValue)
281             {
282                 try
283                 {
284                     callback.invokeContextCallback(context, this);
285                     return true;
286                 }
287                 catch (Exception e)
288                 {
289                     throw new FacesException(e);
290                 }
291             }
292     
293             // Now Look throught facets on this UIComponent
294             for (Iterator<UIComponent> it = this.getFacets().values().iterator(); !returnValue && it.hasNext();)
295             {
296                 returnValue = it.next().invokeOnComponent(context, clientId, callback);
297             }
298     
299             if (returnValue)
300             {
301                 return returnValue;
302             }
303             
304             // is the component an inner component?
305             if (clientId.startsWith(baseClientId))
306             {
307                 // Check if the clientId for the component, which we 
308                 // are looking for, has a rowIndex attached
309                 char separator = UINamingContainer.getSeparatorChar(context);
310                 ValueExpression rowKeyVE = getValueExpression("rowKey");
311                 boolean rowKeyFound = false;
312                 
313                 if (rowKeyVE != null)
314                 {
315                     int oldRow = this.getRowIndex();
316                     try
317                     {
318                         // iterate over the rows
319                         int rowsToProcess = getRows();
320                         // if getRows() returns 0, all rows have to be processed
321                         if (rowsToProcess == 0)
322                         {
323                             rowsToProcess = getRowCount();
324                         }
325                         int rowIndex = getFirst();
326                         for (int rowsProcessed = 0; rowsProcessed < rowsToProcess; rowsProcessed++, rowIndex++)
327                         {
328                             setRowIndex(rowIndex);
329                             if (!isRowAvailable())
330                             {
331                                 break;
332                             }
333                             
334                             if (clientId.startsWith(getContainerClientId(context)))
335                             {
336                                 rowKeyFound = true;
337                                 break;
338                             }
339                         }
340                         
341                         if (rowKeyFound)
342                         {
343                             for (Iterator<UIComponent> it1 = getChildren().iterator(); 
344                                     !returnValue && it1.hasNext();)
345                             {
346                                 //recursive call to find the component
347                                 returnValue = it1.next().invokeOnComponent(context, clientId, callback);
348                             }
349                         }
350                     }
351                     finally
352                     {
353                         this.setRowIndex(oldRow);
354                     }
355                 }
356                 //If the char next to baseClientId is the separator one and
357                 //the subId matches the regular expression
358                 if (rowKeyVE == null && clientId.matches(baseClientId + separator+"[0-9]+"+separator+".*"))
359                 {
360                     String subId = clientId.substring(baseClientId.length() + 1);
361                     String clientRow = subId.substring(0, subId.indexOf(separator));
362         
363                     //Now we save the current position
364                     int oldRow = this.getRowIndex();
365                     
366                     // try-finally --> make sure, that the old row index is restored
367                     try
368                     {
369                         //The conversion is safe, because its already checked on the
370                         //regular expresion
371                         this.setRowIndex(Integer.parseInt(clientRow));
372                         
373                         // check, if the row is available
374                         if (!isRowAvailable())
375                         {
376                             return false;
377                         }
378             
379                         for (Iterator<UIComponent> it1 = getChildren().iterator(); 
380                                 !returnValue && it1.hasNext();)
381                         {
382                             //recursive call to find the component
383                             returnValue = it1.next().invokeOnComponent(context, clientId, callback);
384                         }
385                     }
386                     finally
387                     {
388                         //Restore the old position. Doing this prevent
389                         //side effects.
390                         this.setRowIndex(oldRow);
391                     }
392                 }
393             }
394         }
395         finally
396         {
397             //all components must call popComponentFromEl after visiting is finished
398             popComponentFromEL(context);
399             if (!isCachedFacesContext)
400             {
401                 setTemporalFacesContext(null);
402             }
403         }
404 
405         return returnValue;
406     }
407     
408     @Override
409     public boolean visitTree(VisitContext context, VisitCallback callback)
410     {
411         if (!isVisitable(context))
412         {
413             return false;
414         }
415 
416         boolean isTemporalFacesContext = isTemporalFacesContext();
417         if (!isTemporalFacesContext)
418         {
419             setTemporalFacesContext(context.getFacesContext());
420         }
421         // save the current row index
422         int oldRowIndex = getRowIndex();
423         // set row index to -1 to process the facets and to get the rowless clientId
424         setRowIndex(-1);
425         // push the Component to EL
426         pushComponentToEL(context.getFacesContext(), this);
427         try
428         {
429             VisitResult visitResult = context.invokeVisitCallback(this,
430                     callback);
431             switch (visitResult)
432             {
433             //we are done nothing has to be processed anymore
434             case COMPLETE:
435                 return true;
436 
437             case REJECT:
438                 return false;
439 
440                 //accept
441             default:
442                 // determine if we need to visit our children 
443                 Collection<String> subtreeIdsToVisit = context
444                         .getSubtreeIdsToVisit(this);
445                 boolean doVisitChildren = subtreeIdsToVisit != null
446                         && !subtreeIdsToVisit.isEmpty();
447                 if (doVisitChildren)
448                 {
449                     // visit the facets of the component
450                     for (UIComponent facet : getFacets().values())
451                     {
452                         if (facet.visitTree(context, callback))
453                         {
454                             return true;
455                         }
456                     }
457                     Boolean skipIterationHint = (Boolean) context.getFacesContext().getAttributes().get(SKIP_ITERATION_HINT);
458                     if (skipIterationHint != null && skipIterationHint.booleanValue())
459                     {
460                         // If SKIP_ITERATION is enabled, do not take into account rows.
461                         if (getChildCount() > 0) {
462                             for (UIComponent child : getChildren()) {
463                                 if (child.visitTree(context, callback)) {
464                                     return true;
465                                 }
466                             }
467                         }
468                     }
469                     else
470                     {
471                         // iterate over the rows
472                         int rowsToProcess = getRows();
473                         // if getRows() returns 0, all rows have to be processed
474                         if (rowsToProcess == 0)
475                         {
476                             rowsToProcess = getRowCount();
477                         }
478                         int rowIndex = getFirst();
479                         for (int rowsProcessed = 0; rowsProcessed < rowsToProcess; rowsProcessed++, rowIndex++)
480                         {
481                             setRowIndex(rowIndex);
482                             if (!isRowAvailable())
483                             {
484                                 return false;
485                             }
486                             // visit the children of every child of the UIData that is an instance of UIColumn
487                             for (UIComponent child : getChildren())
488                             {
489                                 if (child.visitTree(context, callback))
490                                 {
491                                     return true;
492                                 }
493                             }
494                         }
495                     }
496                 }
497             }
498         }
499         finally
500         {
501             // pop the component from EL and restore the old row index
502             popComponentFromEL(context.getFacesContext());
503             setRowIndex(oldRowIndex);
504             if (!isTemporalFacesContext)
505             {
506                 setTemporalFacesContext(null);
507             }
508         }
509 
510         // Return false to allow the visiting to continue
511         return false;
512     }    
513     
514     @Override
515     protected FacesContext getFacesContext()
516     {
517         if (_facesContext == null)
518         {
519             return super.getFacesContext();
520         }
521         else
522         {
523             return _facesContext;
524         }
525     }
526     
527     private boolean isTemporalFacesContext()
528     {
529         return _facesContext != null;
530     }
531     
532     private void setTemporalFacesContext(FacesContext facesContext)
533     {
534         _facesContext = facesContext;
535     }
536 
537     /**
538      * A parameter name, under which the rowCount is set in request 
539      * scope similar to the var parameter.
540      * 
541      * @JSFProperty
542      */
543     public abstract String getRowCountVar();
544     
545     /**
546      *  A parameter name, under which the current rowIndex is set in 
547      *  request scope similar to the var parameter.
548      * 
549      * @JSFProperty
550      */
551     public abstract String getRowIndexVar();
552 
553     /**
554      * simple|unorderedList|orderedList
555      * <ul>
556      *   <li>simple = for each dataRow all children are simply rendered</li>
557      *   <li>unorderedList = the list is rendered as HTML unordered list (= bullet list)</li>
558      *   <li>orderedList = the list is rendered as HTML ordered list</li>
559      * </ul>
560      * Default: simple
561      * 
562      * @JSFProperty
563      */
564     public abstract String getLayout();
565     
566     /**
567      * CSS class to be applied to individual items in the list
568      * 
569      * @JSFProperty
570      */
571     public abstract String getItemStyleClass();
572     
573     /**
574      * OnClick handler to be applied to individual items in the list
575      * 
576      * @JSFProperty
577      */
578     public abstract String getItemOnClick();
579 }