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.component.html.ext;
20  
21  import java.io.IOException;
22  import java.sql.ResultSet;
23  import java.util.ArrayList;
24  import java.util.Collection;
25  import java.util.HashMap;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.StringTokenizer;
30  
31  import javax.faces.application.Application;
32  import javax.faces.component.EditableValueHolder;
33  import javax.faces.component.NamingContainer;
34  import javax.faces.component.UIColumn;
35  import javax.faces.component.UIComponent;
36  import javax.faces.context.FacesContext;
37  import javax.faces.el.ValueBinding;
38  import javax.faces.model.DataModel;
39  
40  import org.apache.commons.logging.Log;
41  import org.apache.commons.logging.LogFactory;
42  import org.apache.myfaces.component.NewspaperTable;
43  import org.apache.myfaces.component.UserRoleAware;
44  import org.apache.myfaces.component.UserRoleUtils;
45  import org.apache.myfaces.custom.column.HtmlSimpleColumn;
46  import org.apache.myfaces.custom.crosstable.UIColumns;
47  import org.apache.myfaces.custom.sortheader.HtmlCommandSortHeader;
48  import org.apache.myfaces.renderkit.html.ext.HtmlTableRenderer;
49  import org.apache.myfaces.renderkit.html.util.TableContext;
50  import org.apache.myfaces.shared_tomahawk.renderkit.JSFAttr;
51  import org.apache.myfaces.shared_tomahawk.util.ClassUtils;
52  
53  /**
54   * The MyFacesDataTable extends the standard JSF DataTable by two
55   * important features:
56   * <br/>
57   * <ul>
58   *   <li>Possiblity to save the state of the DataModel.</li>
59   * 
60   *   <li>Support for clickable sort headers (see SortHeader
61   *   component).</li>
62   * </ul>
63   * <br/>
64   * Extended data_table that adds some additional features to the 
65   * standard data_table action: see attribute descriptions for 
66   * preserveDataModel, sortColumn, sortAscending and preserveSort. 
67   * <br/>
68   * Unless otherwise specified, all attributes accept static values or EL expressions.
69   * 
70   * @JSFComponent
71   *   name = "t:dataTable"
72   *   class = "org.apache.myfaces.component.html.ext.HtmlDataTable"
73   *   tagClass = "org.apache.myfaces.generated.taglib.html.ext.HtmlDataTableTag"
74   * @since 1.1.7
75   * @author Thomas Spiegl (latest modification by $Author: lu4242 $)
76   * @author Manfred Geiler
77   * @version $Revision: 1124510 $ $Date: 2011-05-18 21:00:26 -0500 (Wed, 18 May 2011) $
78   */
79  public abstract class AbstractHtmlDataTable extends HtmlDataTableHack implements UserRoleAware, NewspaperTable
80  {
81      private static final Log log = LogFactory.getLog(AbstractHtmlDataTable.class);
82  
83      private static final int PROCESS_DECODES = 1;
84      private static final int PROCESS_VALIDATORS = 2;
85      private static final int PROCESS_UPDATES = 3;
86  
87      private static final boolean DEFAULT_SORTASCENDING = true;
88      private static final boolean DEFAULT_SORTABLE = false;
89      private static final boolean DEFAULT_EMBEDDED = false;
90      private static final boolean DEFAULT_DETAILSTAMP_EXPANDED = false;
91      private static final Class OBJECT_ARRAY_CLASS = (new Object[0]).getClass();
92  
93      private static final Integer DEFAULT_NEWSPAPER_COLUMNS = new Integer(1);
94      private static final String DEFAULT_NEWSPAPER_ORIENTATION = "vertical";
95      private static final String DEFAULT_DETAILSTAMP_LOCATION = "after";
96  
97      /**
98       * the property names
99       */
100     public static final String NEWSPAPER_COLUMNS_PROPERTY = "newspaperColumns";
101     public static final String SPACER_FACET_NAME = "spacer";
102     public static final String NEWSPAPER_ORIENTATION_PROPERTY = "newspaperOrientation";
103 
104     public static final String DETAIL_STAMP_FACET_NAME = "detailStamp";
105 
106     private _SerializableDataModel _preservedDataModel;
107 
108     private String _forceIdIndexFormula = null;
109     private String _sortColumn = null;
110     private Boolean _sortAscending = null;
111     private String _sortProperty = null;
112     private String _rowStyleClass = null;
113     private String _rowStyle = null;
114     private String _varDetailToggler = null;
115 
116     private int _sortColumnIndex = -1;
117 
118     private boolean _isValidChildren = true;
119 
120     private Map _expandedNodes = new HashMap();
121 
122     //private Map _detailRowStates = new HashMap();
123 
124     private TableContext _tableContext = null;
125 
126     public TableContext getTableContext()
127     {
128         if (_tableContext == null)
129         {
130             _tableContext = new TableContext();
131         }
132         return _tableContext;
133     }
134     
135     public void setDetailStamp(UIComponent facet)
136     {
137         getFacets().put(DETAIL_STAMP_FACET_NAME, facet);
138     }
139 
140     /**
141      * This facet renders an additional row after or before (according
142      * to detailStampLocation value) the current row, usually containing
143      * additional information of the related row. It is toggled usually
144      * using varDetailToggle variable and the method toggleDetail().
145      * 
146      * @JSFFacet name="detailStamp"
147      */
148     public UIComponent getDetailStamp()
149     {
150         return (UIComponent) getFacets().get(DETAIL_STAMP_FACET_NAME);
151     }
152     
153     public String getClientId(FacesContext context)
154     {
155         String standardClientId = super.getClientId(context);
156         int rowIndex = getRowIndex();
157         if (rowIndex == -1)
158         {
159             return standardClientId;
160         }
161 
162         String forcedIdIndex = getForceIdIndexFormula();
163         if (forcedIdIndex == null || forcedIdIndex.length() == 0)
164             return standardClientId;
165 
166         // Trick : Remove the last part starting with NamingContainer.SEPARATOR_CHAR that contains the rowIndex.
167         // It would be best to not resort to String manipulation,
168         // but we can't get super.super.getClientId() :-(
169         int indexLast_ = standardClientId.lastIndexOf(NamingContainer.SEPARATOR_CHAR);
170         if (indexLast_ == -1)
171         {
172             log.info("Could not parse super.getClientId. forcedIdIndex will contain the rowIndex.");
173             return standardClientId + NamingContainer.SEPARATOR_CHAR + forcedIdIndex;
174         }
175 
176         //noinspection UnnecessaryLocalVariable
177         String parsedForcedClientId = standardClientId.substring(0, indexLast_ + 1) + forcedIdIndex;
178         return parsedForcedClientId;
179     }
180 
181     public UIComponent findComponent(String expr)
182     {
183         if (expr.length() > 0 && Character.isDigit(expr.charAt(0)))
184         {
185             int separatorIndex = expr.indexOf(NamingContainer.SEPARATOR_CHAR);
186 
187             String rowIndexStr = expr;
188             String remainingPart = null;
189 
190             if (separatorIndex != -1)
191             {
192                 rowIndexStr = expr.substring(0, separatorIndex);
193                 remainingPart = expr.substring(separatorIndex + 1);
194             }
195 
196             int rowIndex = Integer.valueOf(rowIndexStr).intValue();
197 
198             if (remainingPart == null)
199             {
200                 log.error("Wrong syntax of expression : " + expr +
201                         " rowIndex was provided, but no component name.");
202                 return null;
203             }
204 
205             UIComponent comp = super.findComponent(remainingPart);
206 
207             if (comp == null)
208                 return null;
209 
210             //noinspection UnnecessaryLocalVariable
211             UIComponentPerspective perspective = new UIComponentPerspective(this, comp, rowIndex);
212             return perspective;
213         }
214         else
215         {
216             return super.findComponent(expr);
217         }
218     }
219 
220     public void setRowIndex(int rowIndex)
221     {
222         //FacesContext facesContext = FacesContext.getCurrentInstance();
223 
224         if (rowIndex < -1)
225         {
226             throw new IllegalArgumentException("rowIndex is less than -1");
227         }
228 
229         //UIComponent facet = getFacet(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME);
230         /*Just for obtaining an iterator which must be passed to saveDescendantComponentStates()*/
231         //Set set = new HashSet();
232         //set.add(facet);
233         //if (getRowIndex() != -1 && facet != null)
234         //{
235         //    _detailRowStates.put(getClientId(facesContext), saveDescendantComponentStates(set.iterator(), false));
236         //}
237 
238         String rowIndexVar = getRowIndexVar();
239         String rowCountVar = getRowCountVar();
240         String previousRowDataVar = getPreviousRowDataVar();
241         if (rowIndexVar != null || rowCountVar != null || previousRowDataVar != null)
242         {
243             Map requestMap = FacesContext.getCurrentInstance().getExternalContext().getRequestMap();
244 
245             if (previousRowDataVar != null && rowIndex >= 0) //we only need to provide the previousRowDataVar for a valid rowIndex
246             {
247                 if (isRowAvailable())
248                 {
249                     //previous row is available
250                     requestMap.put(previousRowDataVar, getRowData());
251                 }
252                 else
253                 {
254                     //no previous row available
255                     requestMap.put(previousRowDataVar, null);
256                 }
257             }
258 
259             super.setRowIndex(rowIndex);
260 
261             if (rowIndex >= 0)
262             {
263                 //regular row index, update request scope variables
264                 if (rowIndexVar != null)
265                 {
266                     requestMap.put(rowIndexVar, new Integer(rowIndex));
267                 }
268 
269                 if (rowCountVar != null)
270                 {
271                     requestMap.put(rowCountVar, new Integer(getRowCount()));
272                 }
273             }
274             else
275             {
276                 //rowIndex == -1 means end of loop --> remove request scope variables
277                 if (rowIndexVar != null)
278                 {
279                     requestMap.remove(rowIndexVar);
280                 }
281 
282                 if (rowCountVar != null)
283                 {
284                     requestMap.remove(rowCountVar);
285                 }
286 
287                 if (previousRowDataVar != null)
288                 {
289                     requestMap.remove(previousRowDataVar);
290                 }
291             }
292         }
293         else
294         {
295             // no extended var attributes defined, no special treatment
296             super.setRowIndex(rowIndex);
297         }
298 
299         //if (rowIndex != -1 && facet != null)
300         //{
301         //    Object rowState = _detailRowStates.get(getClientId(facesContext));
302 
303         //    restoreDescendantComponentStates(set.iterator(),
304         //            rowState, false);
305 
306         //}
307 
308         if (_varDetailToggler != null)
309         {
310             Map requestMap = getFacesContext().getExternalContext().getRequestMap();
311             requestMap.put(_varDetailToggler, this);
312         }
313     }
314 
315     protected Object saveDescendantComponentStates()
316     {
317         if (!getFacets().isEmpty())
318         {
319             UIComponent detailStampFacet = getFacet(DETAIL_STAMP_FACET_NAME); 
320             if (detailStampFacet != null)
321             {
322                 return saveDescendantComponentStates(new _DetailStampFacetAndChildrenIterator(detailStampFacet, getChildren()), false);
323             }
324         }
325         return super.saveDescendantComponentStates();
326     }
327     
328     protected void restoreDescendantComponentStates(Object state)
329     {
330         if (!getFacets().isEmpty())
331         {
332             UIComponent detailStampFacet = getFacet(DETAIL_STAMP_FACET_NAME); 
333             if (detailStampFacet != null)
334             {
335                 restoreDescendantComponentStates(new _DetailStampFacetAndChildrenIterator(detailStampFacet, getChildren()), state, false);
336                 return;
337             }
338         }
339         super.restoreDescendantComponentStates(state);
340     }
341 
342     public void processDecodes(FacesContext context)
343     {
344         if (!isRendered())
345         {
346             return;
347         }
348 
349         // We must remove and then replace the facet so that
350         // it is not processed by default facet handling code
351         //
352         //Object facet = getFacets().remove(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME);
353         //super.processDecodes(context);
354         //if ( facet != null ) getFacets().put(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME, (UIComponent)facet);
355         setRowIndex(-1);
356         processFacets(context, PROCESS_DECODES);
357         processColumnFacets(context, PROCESS_DECODES);
358         processColumnChildren(context, PROCESS_DECODES);
359         setRowIndex(-1);
360         try
361         {
362             decode(context);
363         }
364         catch (RuntimeException e)
365         {
366             context.renderResponse();
367             throw e;
368         }
369 
370         setRowIndex(-1);
371         processColumns(context, PROCESS_DECODES);
372         setRowIndex(-1);
373         processDetails(context, PROCESS_DECODES);
374         setRowIndex(-1);
375     }
376     
377     private void processFacets(FacesContext context, int processAction)
378     {
379         for (Iterator it = getFacets().entrySet().iterator(); it.hasNext(); )
380         {
381             Map.Entry entry = (Map.Entry) it.next();
382             if (!DETAIL_STAMP_FACET_NAME.equals((String)entry.getKey()))
383             {
384                 process(context, (UIComponent) entry.getValue(), processAction);
385             }
386         }
387     }
388 
389     /**
390      * Invoke the specified phase on all facets of all UIColumn children of this component. Note that no methods are
391      * called on the UIColumn child objects themselves.
392      * 
393      * @param context
394      *            is the current faces context.
395      * @param processAction
396      *            specifies a JSF phase: decode, validate or update.
397      */
398     private void processColumnFacets(FacesContext context, int processAction)
399     {
400         for (Iterator childIter = getChildren().iterator(); childIter.hasNext();)
401         {
402             UIComponent child = (UIComponent) childIter.next();
403             if (child instanceof UIColumn)
404             {
405                 if (!child.isRendered())
406                 {
407                     //Column is not visible
408                     continue;
409                 }
410                 for (Iterator facetsIter = child.getFacets().values()
411                         .iterator(); facetsIter.hasNext();)
412                 {
413                     UIComponent facet = (UIComponent) facetsIter.next();
414                     process(context, facet, processAction);
415                 }
416             }
417         }
418     }
419 
420     /**
421      * Invoke the specified phase on all non-facet children of all UIColumn children of this component. Note that no
422      * methods are called on the UIColumn child objects themselves.
423      * 
424      * @param context
425      *            is the current faces context.
426      * @param processAction
427      *            specifies a JSF phase: decode, validate or update.
428      */
429     private void processColumnChildren(FacesContext context, int processAction)
430     {
431         int first = getFirst();
432         int rows = getRows();
433         int last;
434         if (rows == 0)
435         {
436             last = getRowCount();
437         }
438         else
439         {
440             last = first + rows;
441         }
442         for (int rowIndex = first; last==-1 || rowIndex < last; rowIndex++)
443         {
444             setRowIndex(rowIndex);
445 
446             //scrolled past the last row
447             if (!isRowAvailable())
448                 break;
449 
450             for (Iterator it = getChildren().iterator(); it.hasNext();)
451             {
452                 UIComponent child = (UIComponent) it.next();
453                 if (child instanceof UIColumn)
454                 {
455                     if (!child.isRendered())
456                     {
457                         //Column is not visible
458                         continue;
459                     }
460                     for (Iterator columnChildIter = child.getChildren()
461                             .iterator(); columnChildIter.hasNext();)
462                     {
463                         UIComponent columnChild = (UIComponent) columnChildIter
464                                 .next();
465                         process(context, columnChild, processAction);
466                     }
467                 }
468             }
469         }
470     }
471 
472     /**
473      * @param context
474      * @param processAction
475      */
476     private void processDetails(FacesContext context, int processAction)
477     {
478         UIComponent facet = getFacet(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME);
479 
480         if (facet != null)
481         {
482             int first = getFirst();
483             int rows = getRows();
484             int last;
485             if (rows == 0)
486             {
487                 last = getRowCount();
488             }
489             else
490             {
491                 last = first + rows;
492             }
493             for (int rowIndex = first; last == -1 || rowIndex < last; rowIndex++)
494             {
495                 setRowIndex(rowIndex);
496 
497                 //scrolled past the last row
498                 if (!isRowAvailable())
499                 {
500                     break;
501                 }
502 
503                 if (!isCurrentDetailExpanded())
504                 {
505                     continue;
506                 }
507 
508                 // If we are in the decode phase, the values restored into our
509                 // facet in setRowIndex() may be incorrect. This will happen
510                 // if some input fields are rendered in some rows, but not
511                 // rendered in others. In this case the input field components
512                 // will still contain the _submittedValue from the previous row
513                 // that had that input field and _submittedValue will not be set to
514                 // null by the process() method if there was no value submitted.
515                 // Thus, an invalid component state for that row will be saved in
516                 // _detailRowStates. The validation phase will not put a null into
517                 // _sumbittedValue either, b/c the component is not rendered, so
518                 // validation code doesn't run. This erroneous value will propagate all the way
519                 // to the render phase, and result in all rows on the current page being
520                 // rendered with the "stuck" _submittedValue, rather than evaluating the
521                 // value to render for every row.
522                 //
523                 // We can fix this by initializing _submittedValue of all input fields in the facet
524                 // to null before calling the process() method below during the decode phase.
525                 //
526                 if (PROCESS_DECODES == processAction)
527                 {
528                     resetAllSubmittedValues(facet);
529                 }
530 
531                 process(context, facet, processAction);
532 
533                 // This code comes from TOMAHAWK-493, but really the problem was caused by
534                 // TOMAHAWK-1534, by a bad comparison of rowIndex. The solution proposed is override
535                 // the default iterator for save/restore on HtmlDataTableHack.setRowIndex(), to
536                 // include the detailStamp. In this way, we'll be sure that the state is correctly
537                 // saved and the component clientId is reset for all components that requires it
538                 //if ( rowIndex == (last - 1) )
539                 //{
540                 //    Set set = new HashSet();
541                 //    set.add(facet);
542                 //    _detailRowStates.put(
543                 //            getClientId(FacesContext.getCurrentInstance()),
544                 //                saveDescendantComponentStates(set.iterator(),false));
545                 //}
546             }
547         }
548     }
549 
550     private void resetAllSubmittedValues(UIComponent component)
551     {
552         if (component instanceof EditableValueHolder)
553         {
554             ((EditableValueHolder) component).setSubmittedValue(null);
555         }
556 
557         for (Iterator it = component.getFacetsAndChildren(); it.hasNext();)
558         {
559             resetAllSubmittedValues((UIComponent) it.next());
560         }
561     }
562 
563     /**
564      * @param context
565      * @param processAction
566      */
567     private void processColumns(FacesContext context, int processAction)
568     {
569         for (Iterator it = getChildren().iterator(); it.hasNext();)
570         {
571             UIComponent child = (UIComponent) it.next();
572             if (child instanceof UIColumns)
573             {
574                 process(context, child, processAction);
575             }
576         }
577     }
578 
579     private void process(FacesContext context, UIComponent component, int processAction)
580     {
581         switch (processAction)
582         {
583             case PROCESS_DECODES:
584                 component.processDecodes(context);
585                 break;
586             case PROCESS_VALIDATORS:
587                 component.processValidators(context);
588                 break;
589             case PROCESS_UPDATES:
590                 component.processUpdates(context);
591                 break;
592         }
593     }
594 
595     public void processValidators(FacesContext context)
596     {
597         if (!isRendered())
598         {
599             return;
600         }
601         // We must remove and then replace the facet so that
602         // it is not processed by default facet handling code
603         //
604         //Object facet = getFacets().remove(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME);
605         //super.processValidators(context);
606         //if ( facet != null ) getFacets().put(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME,(UIComponent) facet);
607         setRowIndex(-1);
608         processFacets(context, PROCESS_VALIDATORS);
609         processColumnFacets(context, PROCESS_VALIDATORS);
610         processColumnChildren(context, PROCESS_VALIDATORS);
611         setRowIndex(-1);
612 
613         processColumns(context, PROCESS_VALIDATORS);
614         setRowIndex(-1);
615         processDetails(context, PROCESS_VALIDATORS);
616         setRowIndex(-1);
617 
618         if (context.getRenderResponse())
619         {
620             _isValidChildren = false;
621         }
622     }
623 
624     public void processUpdates(FacesContext context)
625     {
626         if (!isRendered())
627         {
628             return;
629         }
630 
631         // We must remove and then replace the facet so that
632         // it is not processed by default facet handling code
633         //
634         //Object facet = getFacets().remove(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME);
635         //super.processUpdates(context);
636         //if ( facet != null ) getFacets().put(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME,(UIComponent) facet);
637         
638         setRowIndex(-1);
639         processFacets(context, PROCESS_UPDATES);
640         processColumnFacets(context, PROCESS_UPDATES);
641         processColumnChildren(context, PROCESS_UPDATES);
642         setRowIndex(-1);
643 
644         processColumns(context, PROCESS_UPDATES);
645         setRowIndex(-1);
646         processDetails(context, PROCESS_UPDATES);
647         setRowIndex(-1);
648 
649         if (isPreserveDataModel())
650         {
651             updateModelFromPreservedDataModel(context);
652         }
653 
654         if (context.getRenderResponse())
655         {
656             _isValidChildren = false;
657         }
658     }
659 
660     private void updateModelFromPreservedDataModel(FacesContext context)
661     {
662         ValueBinding vb = getValueBinding("value");
663         if (vb != null && !vb.isReadOnly(context))
664         {
665             _SerializableDataModel dm = (_SerializableDataModel) getDataModel();
666             Class type = (getValueType() == null) ? 
667                     vb.getType(context) : 
668                         ClassUtils.simpleClassForName(getValueType());
669             Class dmType = dm.getClass();
670             if (DataModel.class.isAssignableFrom(type))
671             {
672                 vb.setValue(context, dm);
673             }
674             else if (List.class.isAssignableFrom(type) || _SerializableListDataModel.class.isAssignableFrom(dmType))
675             {
676                 vb.setValue(context, dm.getWrappedData());
677             }
678             else if (OBJECT_ARRAY_CLASS.isAssignableFrom(type))
679             {
680                 List lst = (List) dm.getWrappedData();
681                 vb.setValue(context, lst.toArray(new Object[lst.size()]));
682             }
683             else if (ResultSet.class.isAssignableFrom(type))
684             {
685                 throw new UnsupportedOperationException(this.getClass().getName()
686                         + " UnsupportedOperationException");
687             }
688             else
689             {
690                 //Assume scalar data model
691                 List lst = (List) dm.getWrappedData();
692                 if (lst!= null && lst.size() > 0)
693                 {
694                     vb.setValue(context, lst.get(0));
695                 }
696                 else
697                 {
698                     vb.setValue(context, null);
699                 }
700             }
701         }
702         _preservedDataModel = null;
703     }
704 
705     public void encodeBegin(FacesContext context) throws IOException
706     {
707         if (!isRendered())
708             return;
709 
710         if (_isValidChildren && !hasErrorMessages(context))
711         {
712             _preservedDataModel = null;
713         }
714 
715         for (Iterator iter = getChildren().iterator(); iter.hasNext();)
716         {
717             UIComponent component = (UIComponent) iter.next();
718             if (component instanceof UIColumns)
719             {
720                 // Merge the columns from the tomahawk dynamic component
721                 // into this object.
722                 ((UIColumns) component).encodeTableBegin(context);
723             }
724         }
725 
726         //replace facet header content component of the columns, with a new command sort header component
727         //if sortable=true, replace it for all, if not just for the columns marked as sortable
728         for (Iterator iter = getChildren().iterator(); iter.hasNext();)
729         {
730             UIComponent component = (UIComponent) iter.next();
731             if (component instanceof UIColumn)
732             {
733                 UIColumn aColumn = (UIColumn) component;
734                 UIComponent headerFacet = aColumn.getHeader();
735 
736                 boolean replaceHeaderFacets = isSortable(); //if the table is sortable, all
737                 //header facets can be changed if needed
738                 String columnName = null;
739                 String propertyName = null;
740                 boolean defaultSorted = false;
741 
742                 if (aColumn instanceof HtmlSimpleColumn)
743                 {
744                     HtmlSimpleColumn asColumn = (HtmlSimpleColumn) aColumn;
745                     propertyName = asColumn.getSortPropertyName();
746                     defaultSorted = asColumn.isDefaultSorted();
747 
748                     if (asColumn.isSortable())
749                         replaceHeaderFacets = true;
750                 }
751 
752                 //replace header facet with a sortable header component if needed
753                 if (replaceHeaderFacets && isSortHeaderNeeded(aColumn, headerFacet))
754                 {
755                     propertyName = propertyName != null ? propertyName : getSortPropertyFromEL(aColumn);
756                     if (propertyName == null)
757                         log.warn("Couldn't determine sort property for column [" + aColumn.getId() + "].");
758 
759                     if (headerFacet != null)
760                     {
761                         HtmlCommandSortHeader sortHeader = createSortHeaderComponent(context, aColumn, headerFacet, propertyName);
762                         columnName = sortHeader.getColumnName();
763 
764                         aColumn.setHeader(sortHeader);
765                         sortHeader.setParent(aColumn);
766                     }
767                 }
768                 else if (headerFacet instanceof HtmlCommandSortHeader)
769                 {
770                     //command sort headers are already in place, just store the column name and sort property name
771                     HtmlCommandSortHeader sortHeader = (HtmlCommandSortHeader) headerFacet;
772                     columnName = sortHeader.getColumnName();
773                     propertyName = sortHeader.getPropertyName();
774 
775                     //if the command sort header component doesn't specify a sort property, determine it
776                     if (propertyName == null)
777                     {
778                         propertyName = getSortPropertyFromEL(aColumn);
779                         sortHeader.setPropertyName(propertyName);
780                     }
781 
782                     if (propertyName == null)
783                         log.warn("Couldn't determine sort property for column [" + aColumn.getId() + "].");
784                 }
785 
786                 //make the column marked as default sorted be the current sorted column
787                 if (defaultSorted && getSortColumn() == null)
788                 {
789                     setSortColumn(columnName);
790                     setSortProperty(propertyName);
791                 }
792             }
793         }
794 
795         // Now invoke the superclass encodeBegin, which will eventually
796         // execute the encodeBegin for the associated renderer.
797         super.encodeBegin(context);
798     }
799 
800     /**
801      *
802      */
803     protected boolean isSortHeaderNeeded(UIColumn parentColumn, UIComponent headerFacet)
804     {
805         return !(headerFacet instanceof HtmlCommandSortHeader);
806     }
807 
808     /**
809      *
810      */
811     protected HtmlCommandSortHeader createSortHeaderComponent(FacesContext context, UIColumn parentColumn, UIComponent initialHeaderFacet, String propertyName)
812     {
813         Application application = context.getApplication();
814 
815         HtmlCommandSortHeader sortHeader = (HtmlCommandSortHeader) application.createComponent(HtmlCommandSortHeader.COMPONENT_TYPE);
816         String id = context.getViewRoot().createUniqueId();
817         sortHeader.setId(id);
818         sortHeader.setColumnName(id);
819         sortHeader.setPropertyName(propertyName);
820         sortHeader.setArrow(true);
821         sortHeader.setImmediate(true); //needed to work when dataScroller is present
822         sortHeader.getChildren().add(initialHeaderFacet);
823         initialHeaderFacet.setParent(sortHeader);
824 
825         return sortHeader;
826     }
827 
828     /**
829      *
830      */
831     protected String getSortPropertyFromEL(UIComponent component)
832     {
833         if (getVar() == null)
834         {
835             log.warn("There is no 'var' specified on the dataTable, sort properties cannot be determined automaticaly.");
836             return null;
837         }
838 
839         for (Iterator iter = component.getChildren().iterator(); iter.hasNext();)
840         {
841             UIComponent aChild = (UIComponent) iter.next();
842             if (aChild.isRendered())
843             {
844                 ValueBinding vb = aChild.getValueBinding("value");
845                 if (vb != null)
846                 {
847                     String expressionString = vb.getExpressionString();
848 
849                     int varIndex = expressionString.indexOf(getVar() + ".");
850                     if (varIndex != -1)
851                     {
852                         int varEndIndex = varIndex + getVar().length();
853                         String tempProp = expressionString.substring(varEndIndex + 1, expressionString.length());
854 
855                         StringTokenizer tokenizer = new StringTokenizer(tempProp, " +[]{}-/*|?:&<>!=()%");
856                         if (tokenizer.hasMoreTokens())
857                             return tokenizer.nextToken();
858                     }
859                 }
860                 else
861                 {
862                     String sortProperty = getSortPropertyFromEL(aChild);
863                     if (sortProperty != null)
864                         return sortProperty;
865                 }
866             }
867         }
868 
869         return null;
870     }
871 
872     /**
873      * @return the index coresponding to the given column name.
874      */
875     protected int columnNameToIndex(String columnName)
876     {
877         int index = 0;
878         for (Iterator iter = getChildren().iterator(); iter.hasNext();)
879         {
880             UIComponent aChild = (UIComponent) iter.next();
881             if (aChild instanceof UIColumn)
882             {
883                 UIComponent headerFacet = ((UIColumn) aChild).getHeader();
884                 if (headerFacet != null && headerFacet instanceof HtmlCommandSortHeader)
885                 {
886                     HtmlCommandSortHeader sortHeader = (HtmlCommandSortHeader) headerFacet;
887                     if (columnName != null && columnName.equals(sortHeader.getColumnName()))
888                         return index;
889                 }
890             }
891 
892             index += 1;
893         }
894 
895         return -1;
896     }
897 
898     /**
899      * @see javax.faces.component.UIData#encodeEnd(javax.faces.context.FacesContext)
900      */
901     public void encodeEnd(FacesContext context) throws IOException
902     {
903         super.encodeEnd(context);
904         for (Iterator iter = getChildren().iterator(); iter.hasNext();)
905         {
906             UIComponent component = (UIComponent) iter.next();
907             if (component instanceof UIColumns)
908             {
909                 ((UIColumns) component).encodeTableEnd(context);
910             }
911         }
912     }
913 
914     /**
915      * The index of the first row to be displayed, where 0 is the first row.
916      * 
917      * @JSFProperty
918      */
919     public int getFirst()
920     {
921         if (_preservedDataModel != null)
922         {
923             //Rather get the currently restored DataModel attribute
924             return _preservedDataModel.getFirst();
925         }
926         else
927         {
928             return super.getFirst();
929         }
930     }
931 
932     public void setFirst(int first)
933     {
934         if (_preservedDataModel != null)
935         {
936             //Also change the currently restored DataModel attribute
937             _preservedDataModel.setFirst(first);
938         }
939         super.setFirst(first);
940     }
941 
942     /**
943      *  The number of rows to be displayed. Specify zero for all remaining rows in the table.
944      * 
945      * @JSFProperty
946      */
947     public int getRows()
948     {
949         if (_preservedDataModel != null)
950         {
951             //Rather get the currently restored DataModel attribute
952             return _preservedDataModel.getRows();
953         }
954         else
955         {
956             return super.getRows();
957         }
958     }
959 
960     public void setRows(int rows)
961     {
962         if (_preservedDataModel != null)
963         {
964             //Also change the currently restored DataModel attribute
965             _preservedDataModel.setRows(rows);
966         }
967         super.setRows(rows);
968     }
969 
970     public Object saveState(FacesContext context)
971     {
972         boolean preserveSort = isPreserveSort();
973 
974         Object[] values = new Object[15];
975         values[0] = super.saveState(context);
976         values[1] = _preserveDataModel;
977 
978         if (isPreserveDataModel())
979         {
980             _preservedDataModel = getSerializableDataModel();
981             values[2] = saveAttachedState(context, _preservedDataModel);
982         }
983         else
984         {
985             values[2] = null;
986         }
987         values[3] = _preserveSort;
988         values[4] = _forceIdIndexFormula;
989         values[5] = _sortColumn;
990         values[6] = _sortAscending;
991         values[7] = _sortProperty;
992 
993         values[8] = _rowStyleClass;
994         values[9] = _rowStyle;
995 
996         values[10] = preserveSort ? getSortColumn() : null;
997         values[11] = preserveSort ? Boolean.valueOf(isSortAscending()) : null;
998 
999         values[12] = _varDetailToggler;
1000         values[13] = _expandedNodes;
1001         values[14] = new Integer(_sortColumnIndex);
1002 
1003         return values;
1004     }
1005 
1006     /**
1007      * @see org.apache.myfaces.component.html.ext.HtmlDataTableHack#getDataModel()
1008      */
1009     protected DataModel getDataModel()
1010     {
1011         if (_preservedDataModel != null)
1012         {
1013             setDataModel(_preservedDataModel);
1014             _preservedDataModel = null;
1015         }
1016 
1017         return super.getDataModel();
1018     }
1019 
1020     /**
1021      * @see org.apache.myfaces.component.html.ext.HtmlDataTableHack#createDataModel()
1022      */
1023     protected DataModel createDataModel()
1024     {
1025         DataModel dataModel = super.createDataModel();
1026 
1027         boolean isSortable = isSortable();
1028 
1029         if (!(dataModel instanceof SortableModel))
1030         {
1031             //if sortable=true make each column sortable
1032             //if sortable=false, check to see if at least one column sortable case in which
1033             //the current model needs to be wrapped by a sortable one.
1034             for (Iterator iter = getChildren().iterator(); iter.hasNext();)
1035             {
1036                 UIComponent component = (UIComponent) iter.next();
1037                 if (component instanceof HtmlSimpleColumn)
1038                 {
1039                     HtmlSimpleColumn aColumn = (HtmlSimpleColumn) component;
1040                     if (isSortable())
1041                         aColumn.setSortable(true);
1042 
1043                     if (aColumn.isSortable())
1044                         isSortable = true;
1045 
1046                     if (aColumn.isDefaultSorted() && getSortColumn() == null)
1047                         setSortProperty(aColumn.getSortPropertyName());
1048                 }
1049             }
1050 
1051             if (isSortable)
1052                 dataModel = new SortableModel(dataModel);
1053         }
1054 
1055         if (isSortable && getSortProperty() != null)
1056         {
1057             SortCriterion criterion = new SortCriterion(getSortProperty(), isSortAscending());
1058             List criteria = new ArrayList();
1059             criteria.add(criterion);
1060 
1061             ((SortableModel) dataModel).setSortCriteria(criteria);
1062         }
1063 
1064         return dataModel;
1065     }
1066 
1067     public void restoreState(FacesContext context, Object state)
1068     {
1069         Object[] values = (Object[]) state;
1070         super.restoreState(context, values[0]);
1071         _preserveDataModel = (Boolean) values[1];
1072         if (isPreserveDataModel())
1073         {
1074             _preservedDataModel = (_SerializableDataModel) restoreAttachedState(context, values[2]);
1075         }
1076         else
1077         {
1078             _preservedDataModel = null;
1079         }
1080         _preserveSort = (Boolean) values[3];
1081         _forceIdIndexFormula = (String) values[4];
1082         _sortColumn = (String) values[5];
1083         _sortAscending = (Boolean) values[6];
1084         _sortProperty = (String) values[7];
1085 
1086         _rowStyleClass = (String) values[8];
1087         _rowStyle = (String) values[9];
1088 
1089         if (isPreserveSort())
1090         {
1091             String sortColumn = (String) values[10];
1092             Boolean sortAscending = (Boolean) values[11];
1093             if (sortColumn != null && sortAscending != null)
1094             {
1095                 ValueBinding vb = getValueBinding("sortColumn");
1096                 if (vb != null && !vb.isReadOnly(context))
1097                 {
1098                     vb.setValue(context, sortColumn);
1099                 }
1100 
1101                 vb = getValueBinding("sortAscending");
1102                 if (vb != null && !vb.isReadOnly(context))
1103                 {
1104                     vb.setValue(context, sortAscending);
1105                 }
1106             }
1107         }
1108 
1109         _varDetailToggler = (String) values[12];
1110         _expandedNodes = (Map) values[13];
1111         _sortColumnIndex = values[14] != null ? ((Integer) values[14]).intValue() : -1;
1112     }
1113 
1114     public _SerializableDataModel getSerializableDataModel()
1115     {
1116         DataModel dm = getDataModel();
1117         if (dm instanceof _SerializableDataModel)
1118         {
1119             return (_SerializableDataModel) dm;
1120         }
1121         return createSerializableDataModel();
1122     }
1123 
1124     /**
1125      * @return _SerializableDataModel
1126      */
1127     private _SerializableDataModel createSerializableDataModel()
1128     {
1129         Object value = getValue();
1130         if (value == null)
1131         {
1132             return null;
1133         }
1134         else if (value instanceof DataModel)
1135         {
1136             return new _SerializableDataModel(getFirst(), getRows(), (DataModel) value);
1137         }
1138         else if (value instanceof List)
1139         {
1140             return new _SerializableListDataModel(getFirst(), getRows(), (List) value);
1141         }
1142         // accept a Collection is not supported in the Spec
1143         else if (value instanceof Collection)
1144         {
1145             return new _SerializableListDataModel(getFirst(), getRows(), new ArrayList((Collection) value));
1146         }
1147         else if (OBJECT_ARRAY_CLASS.isAssignableFrom(value.getClass()))
1148         {
1149             return new _SerializableArrayDataModel(getFirst(), getRows(), (Object[]) value);
1150         }
1151         else if (value instanceof ResultSet)
1152         {
1153             return new _SerializableResultSetDataModel(getFirst(), getRows(), (ResultSet) value);
1154         }
1155         else if (value instanceof javax.servlet.jsp.jstl.sql.Result)
1156         {
1157             return new _SerializableResultDataModel(getFirst(), getRows(),
1158                     (javax.servlet.jsp.jstl.sql.Result) value);
1159         }
1160         else
1161         {
1162             return new _SerializableScalarDataModel(getFirst(), getRows(), value);
1163         }
1164     }
1165 
1166     public boolean isRendered()
1167     {
1168         if (!UserRoleUtils.isVisibleOnUserRole(this))
1169             return false;
1170         return super.isRendered();
1171     }
1172 
1173     public void setForceIdIndexFormula(String forceIdIndexFormula)
1174     {
1175         _forceIdIndexFormula = forceIdIndexFormula;
1176         ValueBinding vb = getValueBinding("forceIdIndexFormula");
1177         if (vb != null)
1178         {
1179             vb.setValue(getFacesContext(), _forceIdIndexFormula);
1180             _forceIdIndexFormula = null;
1181         }
1182     }
1183 
1184     /**
1185      * A formula that overrides the default row index in the 
1186      * construction of table's body components. 
1187      * 
1188      * Example : #{myRowVar.key} Warning, the EL should 
1189      * evaluate to a unique value for each row !
1190      * 
1191      * @JSFProperty
1192      */
1193     public String getForceIdIndexFormula()
1194     {
1195         if (_forceIdIndexFormula != null)
1196             return _forceIdIndexFormula;
1197         ValueBinding vb = getValueBinding("forceIdIndexFormula");
1198         if (vb == null)
1199             return null;
1200         Object eval = vb.getValue(getFacesContext());
1201         return eval == null ? null : eval.toString();
1202     }
1203 
1204     /**
1205      * Specify what column the data should be sorted on.
1206      * <p/>
1207      * Note that calling this method <i>immediately</i> stores the value
1208      * via any value-binding with name "sortColumn". This is done because
1209      * this method is called by the HtmlCommandSortHeader component when
1210      * the user has clicked on a column's sort header. In this case, the
1211      * the model getter method mapped for name "value" needs to read this
1212      * value in able to return the data in the desired order - but the
1213      * HtmlCommandSortHeader component is usually "immediate" in order to
1214      * avoid validating the enclosing form. Yes, this is rather hacky -
1215      * but it works.
1216      */
1217     public void setSortColumn(String sortColumn)
1218     {
1219         _sortColumn = sortColumn;
1220         // update model is necessary here, because processUpdates is never called
1221         // reason: HtmlCommandSortHeader.isImmediate() == true
1222         ValueBinding vb = getValueBinding("sortColumn");
1223         if (vb != null)
1224         {
1225             vb.setValue(getFacesContext(), _sortColumn);
1226             _sortColumn = null;
1227         }
1228 
1229         setSortColumnIndex(columnNameToIndex(sortColumn));
1230     }
1231 
1232     /**
1233      * Value reference to a model property that gives the current 
1234      * sort column name. The target String property is set to 
1235      * the "columnName" of whichever column has been chosen 
1236      * to sort by, and the method which is bound to the "value" 
1237      * attribute of this table (ie which provides the DataModel used) 
1238      * is expected to use this property to determine how to sort 
1239      * the DataModel's contents.
1240      * 
1241      * @JSFProperty
1242      */
1243     public String getSortColumn()
1244     {
1245         if (_sortColumn != null) return _sortColumn;
1246         ValueBinding vb = getValueBinding("sortColumn");
1247         return vb != null ? (String) vb.getValue(getFacesContext()) : null;
1248     }
1249 
1250     public void setSortAscending(boolean sortAscending)
1251     {
1252         _sortAscending = Boolean.valueOf(sortAscending);
1253         // update model is necessary here, because processUpdates is never called
1254         // reason: HtmlCommandSortHeader.isImmediate() == true
1255         ValueBinding vb = getValueBinding("sortAscending");
1256         if (vb != null)
1257         {
1258             vb.setValue(getFacesContext(), _sortAscending);
1259             _sortAscending = null;
1260         }
1261     }
1262 
1263     /**
1264      * Value reference to a model property that gives the current 
1265      * sort direction. The target Boolean property is set to true 
1266      * when the selected sortColumn should be sorted in ascending 
1267      * order, and false otherwise. The method which is bound to 
1268      * the "value" attribute of this table (ie which provides the 
1269      * DataModel used) is expected to use this property to 
1270      * determine how to sort the DataModel's contents.
1271      * 
1272      * @JSFProperty
1273      *   defaultValue = "true"
1274      */
1275     public boolean isSortAscending()
1276     {
1277         if (_sortAscending != null)
1278             return _sortAscending.booleanValue();
1279         ValueBinding vb = getValueBinding("sortAscending");
1280         Boolean v = vb != null ? (Boolean) vb.getValue(getFacesContext()) : null;
1281         return v != null ? v.booleanValue() : DEFAULT_SORTASCENDING;
1282     }
1283 
1284     /**
1285      * The name of a javabean property on which the table is sorted.
1286      * <p>
1287      * The datamodel should contain objects that have this property;
1288      * reflection will be used to sort the datamodel on that property
1289      * using the default comparator for that type. 
1290      * <p>
1291      * This value is part of the component state. However it is not
1292      * directly settable by users; instead it is set by other components
1293      * such as a CommandSortHeader.
1294      */
1295     public void setSortProperty(String sortProperty)
1296     {
1297         _sortProperty = sortProperty;
1298     }
1299 
1300     /**
1301      * @JSFProperty
1302      *   literalOnly="true"
1303      *   tagExcluded="true"
1304      */
1305     public String getSortProperty()
1306     {
1307         return _sortProperty;
1308     }
1309 
1310     /**
1311      * Define if the table is sortable or not
1312      * 
1313      * @JSFProperty
1314      *   defaultValue="false"
1315      */
1316     public abstract boolean isSortable();
1317 
1318     /**
1319      * Avoids rendering the html table tags, thus, giving you a 
1320      * table rendering just rows. You can use this together 
1321      * with the detailStamp faces of the parent datatable 
1322      * to render child-tables using the same layout as the parent. 
1323      * 
1324      * Notice: You have to ensure both tables do have the same 
1325      * number of columns. Using the colspan attribute of the 
1326      * column tag might help alot.
1327      * 
1328      * @JSFProperty
1329      *   defaultValue="false"
1330      */
1331     public abstract boolean isEmbedded();
1332 
1333     /**
1334      * true|false - true if the detailStamp should be expanded by default. default: false
1335      * 
1336      * @JSFProperty
1337      *   defaultValue="false"
1338      */
1339     public abstract boolean isDetailStampExpandedDefault();
1340 
1341     /**
1342      * before|after - where to render the detailStamp, before the 
1343      * actual row or after it. default: after
1344      * 
1345      * @JSFProperty
1346      *   defaultValue="after"
1347      */
1348     public abstract String getDetailStampLocation();
1349 
1350     /**
1351      * Defines a JavaScript onmouseover event handler for each table row
1352      * 
1353      * @JSFProperty
1354      */
1355     public abstract String getRowOnMouseOver();
1356 
1357     /**
1358      * Defines a JavaScript onmouseout event handler for each table row
1359      * 
1360      * @JSFProperty
1361      */
1362     public abstract String getRowOnMouseOut();
1363 
1364     /**
1365      * Defines a JavaScript onclick event handler for each table row
1366      * 
1367      * @JSFProperty
1368      */
1369     public abstract String getRowOnClick();
1370 
1371     /**
1372      * Defines a JavaScript ondblclick event handler for each table row
1373      * 
1374      * @JSFProperty
1375      */
1376     public abstract String getRowOnDblClick();
1377 
1378     /**
1379      * Defines a JavaScript onkeydown event handler for each table row
1380      * 
1381      * @JSFProperty
1382      */
1383     public abstract String getRowOnKeyDown();
1384 
1385     /**
1386      * Defines a JavaScript onkeypress event handler for each table row
1387      * 
1388      * @JSFProperty
1389      */
1390     public abstract String getRowOnKeyPress();
1391 
1392     /**
1393      * Defines a JavaScript onkeyup event handler for each table row
1394      * 
1395      * @JSFProperty
1396      */
1397     public abstract String getRowOnKeyUp();
1398 
1399     /**
1400      * Corresponds to the HTML class attribute for the row tr tag.
1401      * 
1402      * @JSFProperty
1403      */
1404     public String getRowStyleClass()
1405     {
1406     if (_rowStyleClass != null)
1407         return _rowStyleClass;
1408 
1409     // TODO: temporarily support fully-qualified ext. dataTable attribute names.
1410     ValueBinding vb = getValueBinding("org.apache.myfaces.dataTable.ROW_STYLECLASS");
1411     if (vb != null)
1412         log.warn("org.apache.myfaces.dataTable.ROW_STYLECLASS is deprecated. Please use rowStyleClass instead.");
1413     else
1414         vb = getValueBinding(JSFAttr.ROW_STYLECLASS_ATTR);
1415     if(vb == null)
1416         return null;
1417     String bindingValue = (String) vb.getValue(getFacesContext());
1418     if(bindingValue == "")
1419         return null;  // Fix for JSF 1.2 EL coercing nulls to empty string
1420     return bindingValue;
1421     }
1422 
1423     public void setRowStyleClass(String rowStyleClass)
1424     {
1425         _rowStyleClass = rowStyleClass;
1426     }
1427 
1428     /**
1429      * Corresponds to the HTML style attribute for the row tr tag.
1430      * 
1431      * @JSFProperty
1432      */
1433     public String getRowStyle()
1434     {
1435         if (_rowStyle != null)
1436             return _rowStyle;
1437 
1438     // TODO: temporarily support fully-qualified ext. dataTable attribute names.
1439         ValueBinding vb = getValueBinding("org.apache.myfaces.dataTable.ROW_STYLE");
1440     if (vb != null)
1441         log.warn("org.apache.myfaces.dataTable.ROW_STYLE is deprecated. Please use rowStyle instead.");
1442     else
1443         vb = getValueBinding(JSFAttr.ROW_STYLE_ATTR);
1444     if(vb == null)
1445         return null;
1446     String bindingValue = (String) vb.getValue(getFacesContext());
1447     if(bindingValue == "")
1448         return null;  // Fix for JSF 1.2 EL coercing nulls to empty string
1449     return bindingValue;
1450     }
1451 
1452     public void setRowStyle(String rowStyle)
1453     {
1454         _rowStyle = rowStyle;
1455     }
1456 
1457     /**
1458      * Defines a JavaScript onmpusedown event handler for each table row
1459      * 
1460      * @JSFProperty
1461      */
1462     public abstract String getRowOnMouseDown();
1463 
1464     /**
1465      * Defines a JavaScript onmousemove event handler for each table row
1466      * 
1467      * @JSFProperty
1468      */
1469     public abstract String getRowOnMouseMove();
1470 
1471     /**
1472      * Defines a JavaScript onmouseup event handler for each table row
1473      * 
1474      * @JSFProperty
1475      */
1476     public abstract String getRowOnMouseUp();
1477 
1478     /**
1479      * @JSFProperty
1480      *   tagExcluded = "true"
1481      */
1482     protected boolean isValidChildren()
1483     {
1484         return _isValidChildren;
1485     }
1486 
1487     protected void setIsValidChildren(boolean isValidChildren)
1488     {
1489         _isValidChildren = isValidChildren;
1490     }
1491 
1492     protected _SerializableDataModel getPreservedDataModel()
1493     {
1494         return _preservedDataModel;
1495     }
1496 
1497     protected void setPreservedDataModel(_SerializableDataModel preservedDataModel)
1498     {
1499         _preservedDataModel = preservedDataModel;
1500     }
1501 
1502 
1503     public boolean isCurrentDetailExpanded()
1504     {
1505         Boolean expanded = (Boolean) _expandedNodes.get(getClientId(getFacesContext()));
1506         if (expanded != null)
1507         {
1508             return expanded.booleanValue();
1509         }
1510 
1511         return isDetailStampExpandedDefault();
1512     }
1513 
1514     public void setVarDetailToggler(String varDetailToggler)
1515     {
1516         _varDetailToggler = varDetailToggler;
1517     }
1518 
1519     /**
1520      *  This variable has the boolean property "currentdetailExpanded" 
1521      *  which is true if the current detail row is expanded and the 
1522      *  action method "toggleDetail" which expand/collapse the current 
1523      *  detail row.
1524      * 
1525      * @JSFProperty
1526      */
1527     public String getVarDetailToggler()
1528     {
1529         if (_varDetailToggler != null)
1530             return _varDetailToggler;
1531         ValueBinding vb = getValueBinding("varDetailToggler");
1532         return vb != null ? (String) vb.getValue(getFacesContext()) : null;
1533     }
1534 
1535     /**
1536      * Corresponds to the HTML style attribute for grouped rows.
1537      *  
1538      * @JSFProperty
1539      */
1540     public abstract String getRowGroupStyle();
1541 
1542     /**
1543      * StyleClass for grouped rows.
1544      * 
1545      * @JSFProperty
1546      */
1547     public abstract String getRowGroupStyleClass();
1548     
1549     /**
1550      * Corresponds to the HTML style attribute for the table body tag
1551      * 
1552      * @JSFProperty
1553      */
1554     public abstract String getBodyStyle();
1555 
1556     /**
1557      * Corresponds to the HTML class attribute for the table body tag.
1558      * 
1559      * @JSFProperty
1560      */
1561     public abstract String getBodyStyleClass();
1562 
1563     public AbstractHtmlDataTable()
1564     {
1565         setRendererType(DEFAULT_RENDERER_TYPE);
1566     }
1567 
1568     /**
1569      * Change the status of the current detail row from collapsed to expanded or
1570      * viceversa.
1571      */
1572     public void toggleDetail()
1573     {
1574         String derivedRowKey = getClientId(getFacesContext());
1575 
1576         // get the current expanded state of the row
1577         boolean expanded = isDetailExpanded();
1578         if (expanded)
1579         {
1580             // toggle to "collapsed"
1581 
1582             if (isDetailStampExpandedDefault())
1583             {
1584                 // if default is expanded we have to override with FALSE here
1585                 _expandedNodes.put(derivedRowKey, Boolean.FALSE);
1586             }
1587             else
1588             {
1589                 // if default is collapsed we can fallback to this default
1590                 _expandedNodes.remove(derivedRowKey);
1591             }
1592         }
1593         else
1594         {
1595             // toggle to "expanded"
1596 
1597             if (isDetailStampExpandedDefault())
1598             {
1599                 // if default is expanded we can fallback to this default
1600                 _expandedNodes.remove(derivedRowKey);
1601             }
1602             else
1603             {
1604                 // if default is collapsed we have to override with TRUE
1605                 _expandedNodes.put(derivedRowKey, Boolean.TRUE);
1606             }
1607         }
1608     }
1609 
1610     /**
1611      * Return true if the current detail row is expanded.
1612      *
1613      * @return true if the current detail row is expanded.
1614      */
1615     public boolean isDetailExpanded()
1616     {
1617         Boolean expanded = (Boolean) _expandedNodes.get(getClientId(getFacesContext()));
1618         if (expanded == null)
1619         {
1620             return isDetailStampExpandedDefault();
1621         }
1622 
1623         return expanded.booleanValue();
1624     }
1625 
1626     public int getSortColumnIndex()
1627     {
1628         if (_sortColumnIndex == -1)
1629             _sortColumnIndex = columnNameToIndex(getSortColumn());
1630 
1631         return _sortColumnIndex;
1632     }
1633 
1634     public void setSortColumnIndex(int sortColumnIndex)
1635     {
1636         _sortColumnIndex = sortColumnIndex;
1637     }
1638 
1639     /**
1640      * The number of columns to wrap the table over. Default: 1
1641      * 
1642      * Set the number of columns the table will be divided over.
1643      * 
1644      * @JSFProperty
1645      *   defaultValue="1"
1646      */
1647     public abstract int getNewspaperColumns();
1648 
1649     /**
1650      * The orientation of the newspaper columns in the newspaper 
1651      * table - "horizontal" or "vertical". Default: vertical
1652      * 
1653      * @JSFProperty
1654      *   defaultValue = "vertical"
1655      */
1656     public abstract String getNewspaperOrientation();
1657 
1658     /**
1659      * Gets the spacer facet, between each pair of newspaper columns.
1660      * 
1661      * @JSFFacet
1662      *   name="spacer"
1663      */
1664     public UIComponent getSpacer()
1665     {
1666         return (UIComponent) getFacets().get(SPACER_FACET_NAME);
1667     }
1668 
1669     public void setSpacer(UIComponent spacer)
1670     {
1671         getFacets().put(SPACER_FACET_NAME, spacer);
1672     }
1673 
1674     /**
1675      * Expand all details
1676      */
1677     public void expandAllDetails()
1678     {
1679         int rowCount = getRowCount();
1680 
1681         _expandedNodes.clear();
1682         
1683         if (getRowKey() != null)
1684         {
1685             int oldRow = getRowIndex();
1686             try
1687             {
1688                 for (int row = 0; row < rowCount; row++)
1689                 {
1690                     setRowIndex(row);
1691                     _expandedNodes.put(getClientId(getFacesContext()), Boolean.TRUE);
1692                 }
1693             }
1694             finally
1695             {
1696                 setRowIndex(oldRow);
1697             }
1698         }
1699         else
1700         {
1701             for (int row = 0; row < rowCount; row++)
1702             {
1703                 _expandedNodes.put(new Integer(row).toString(), Boolean.TRUE);
1704             }
1705         }
1706     }
1707 
1708     /**
1709      * Collapse all details
1710      */
1711     public void collapseAllDetails()
1712     {
1713         _expandedNodes.clear();
1714     }
1715 
1716     /**
1717      * @return true is any of the details is expanded
1718      */
1719     public boolean isExpandedEmpty()
1720     {
1721         boolean expandedEmpty = true;
1722         if (_expandedNodes != null)
1723         {
1724             expandedEmpty = _expandedNodes.isEmpty();
1725         }
1726         return expandedEmpty;
1727     }
1728 
1729     /**
1730      * Clears expanded nodes set if expandedEmpty is true
1731      *
1732      * @param expandedEmpty
1733      */
1734     public void setExpandedEmpty(boolean expandedEmpty)
1735     {
1736         if (expandedEmpty)
1737         {
1738             if (_expandedNodes != null)
1739             {
1740                 _expandedNodes.clear();
1741             }
1742         }
1743     }
1744 
1745     //------------------ GENERATED CODE BEGIN (do not modify!) --------------------
1746 
1747     public static final String COMPONENT_TYPE = "org.apache.myfaces.HtmlDataTable";
1748     public static final String DEFAULT_RENDERER_TYPE = "org.apache.myfaces.Table";
1749 
1750     private static final boolean DEFAULT_PRESERVEDATAMODEL = false;
1751     private static final boolean DEFAULT_PRESERVESORT = true;
1752     private static final boolean DEFAULT_RENDEREDIFEMPTY = true;
1753 
1754     private Boolean _preserveDataModel = null;
1755     private Boolean _preserveSort = null;
1756 
1757     public void setPreserveDataModel(boolean preserveDataModel)
1758     {
1759         _preserveDataModel = Boolean.valueOf(preserveDataModel);
1760     }
1761 
1762     /**
1763      * Indicates whether the state of the whole DataModel should 
1764      * be saved and restored. When set to false, the value-binding 
1765      * for the "value" attribute of this table is executed each 
1766      * time the page is rendered. When set to true, that 
1767      * value-binding is only executed when the component is first 
1768      * created, and the DataModel state is thereafter saved/restored 
1769      * automatically by the component. When column sorting is 
1770      * used for a table this property needs to be false so that 
1771      * the DataModel can be updated to reflect any changes in the 
1772      * sort criteria. Default: false
1773      * 
1774      * @JSFProperty
1775      */
1776     public boolean isPreserveDataModel()
1777     {
1778         if (_preserveDataModel != null)
1779             return _preserveDataModel.booleanValue();
1780         ValueBinding vb = getValueBinding("preserveDataModel");
1781         Boolean v = vb != null ? (Boolean) vb.getValue(getFacesContext()) : null;
1782         return v != null ? v.booleanValue() : DEFAULT_PRESERVEDATAMODEL;
1783     }
1784 
1785     public void setPreserveSort(boolean preserveSort)
1786     {
1787         _preserveSort = Boolean.valueOf(preserveSort);
1788     }
1789     
1790     /**
1791      * Indicates whether the state of the sortColumn and sortAscending 
1792      * attribute should be saved and restored and written back to the 
1793      * model during the update model phase. Default: true
1794      * 
1795      * @JSFProperty
1796      *   defaultValue = "true"
1797      */
1798     public boolean isPreserveSort()
1799     {
1800         if (_preserveSort != null)
1801             return _preserveSort.booleanValue();
1802         ValueBinding vb = getValueBinding("preserveSort");
1803         Boolean v = vb != null ? (Boolean) vb.getValue(getFacesContext()) : null;
1804         return v != null ? v.booleanValue() : DEFAULT_PRESERVESORT;
1805     }
1806 
1807     /**
1808      * Indicates whether this table should be rendered if the 
1809      * underlying DataModel is empty. You could as well use 
1810      * rendered="#{not empty bean.list}", but this one causes 
1811      * the getList method of your model bean beeing called up 
1812      * to five times per request, which is not optimal when 
1813      * the list is backed by a DB table. Using 
1814      * renderedIfEmpty="false" solves this problem, because 
1815      * the MyFaces extended HtmlDataTable automatically caches 
1816      * the DataModel and calles the model getter only once 
1817      * per request. Default: true
1818      * 
1819      * @JSFProperty
1820      *   defaultValue = "true"
1821      */
1822     public abstract boolean isRenderedIfEmpty();
1823 
1824     /**
1825      * A parameter name, under which the current rowIndex is set 
1826      * in request scope similar to the var parameter.
1827      * 
1828      * @JSFProperty
1829      */
1830     public abstract String getRowIndexVar();
1831     
1832     /**
1833      * A parameter name, under which the rowCount is set in 
1834      * request scope similar to the var parameter.
1835      * 
1836      * @JSFProperty
1837      */
1838     public abstract String getRowCountVar();
1839 
1840     /**
1841      * A parameter name, under which the previous RowData Object 
1842      * is set in request scope similar to the rowIndexVar and 
1843      * rowCountVar parameters. Mind that the value of this 
1844      * request scope attribute is null in the first row or 
1845      * when isRowAvailable returns false for the previous row.
1846      * 
1847      * @JSFProperty
1848      */
1849     public abstract String getPreviousRowDataVar();
1850 
1851     /**
1852      * A parameter name, under which the a boolean is set in request 
1853      * scope similar to the var parameter. TRUE for the column that 
1854      * is currently sorted, FALSE otherwise.
1855      * 
1856      * @JSFProperty
1857      */
1858     public abstract String getSortedColumnVar();
1859     
1860     /**
1861      * HTML: Specifies the horizontal alignment of this element. 
1862      * Deprecated in HTML 4.01.
1863      * 
1864      * @JSFProperty
1865      */
1866     public abstract String getAlign();
1867 
1868     /**
1869      * The id to use for
1870      * 
1871      * @JSFProperty
1872      */
1873     public abstract String getRowId();
1874         
1875     /**
1876      * Reserved for future use.
1877      * 
1878      * @JSFProperty
1879      */
1880     public abstract String getDatafld();
1881     
1882     /**
1883      * Reserved for future use.
1884      * 
1885      * @JSFProperty
1886      */
1887     public abstract String getDatasrc();
1888     
1889     /**
1890      * Reserved for future use.
1891      * 
1892      * @JSFProperty
1893      */
1894     public abstract String getDataformatas();
1895 
1896     /**
1897      * Indicate the expected type of the EL expression pointed
1898      * by value property. It is useful when vb.getType() cannot
1899      * found the type, like when a map value is resolved on 
1900      * the expression.
1901      * 
1902      * @JSFProperty
1903      */
1904     public abstract String getValueType(); 
1905 
1906 }