You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by lu...@apache.org on 2011/03/16 22:39:29 UTC

svn commit: r1082312 - in /myfaces/tomahawk/trunk/core20/src: main/java/org/apache/myfaces/component/html/ext/ main/java/org/apache/myfaces/custom/datalist/ test/java/org/apache/myfaces/component/html/ext/

Author: lu4242
Date: Wed Mar 16 21:39:29 2011
New Revision: 1082312

URL: http://svn.apache.org/viewvc?rev=1082312&view=rev
Log:
TOMAHAWK-961 Deleting a row when t:dataList/t:dataTable preserveRowStates=true assigns submitted values to wrong row (see TOMAHAWK-1552 for details)

Added:
    myfaces/tomahawk/trunk/core20/src/main/java/org/apache/myfaces/component/html/ext/_SubIdConverter.java
Modified:
    myfaces/tomahawk/trunk/core20/src/main/java/org/apache/myfaces/component/html/ext/AbstractHtmlDataTable.java
    myfaces/tomahawk/trunk/core20/src/main/java/org/apache/myfaces/component/html/ext/HtmlDataTableHack.java
    myfaces/tomahawk/trunk/core20/src/main/java/org/apache/myfaces/custom/datalist/AbstractHtmlDataList.java
    myfaces/tomahawk/trunk/core20/src/test/java/org/apache/myfaces/component/html/ext/HtmlDataTablePreserveRowComponentStateTest.java

Modified: myfaces/tomahawk/trunk/core20/src/main/java/org/apache/myfaces/component/html/ext/AbstractHtmlDataTable.java
URL: http://svn.apache.org/viewvc/myfaces/tomahawk/trunk/core20/src/main/java/org/apache/myfaces/component/html/ext/AbstractHtmlDataTable.java?rev=1082312&r1=1082311&r2=1082312&view=diff
==============================================================================
--- myfaces/tomahawk/trunk/core20/src/main/java/org/apache/myfaces/component/html/ext/AbstractHtmlDataTable.java (original)
+++ myfaces/tomahawk/trunk/core20/src/main/java/org/apache/myfaces/component/html/ext/AbstractHtmlDataTable.java Wed Mar 16 21:39:29 2011
@@ -120,7 +120,7 @@ public abstract class AbstractHtmlDataTa
 
     private boolean _isValidChildren = true;
 
-    private Map<Integer, Boolean> _expandedNodes = new HashMap<Integer, Boolean>();
+    private Map<String, Boolean> _expandedNodes = new HashMap<String, Boolean>();
 
     //private Map<String, Object> _detailRowStates = new HashMap<String, Object>();
 
@@ -245,7 +245,49 @@ public abstract class AbstractHtmlDataTa
                 // Check if the clientId for the component, which we 
                 // are looking for, has a rowIndex attached
                 char separator = UINamingContainer.getSeparatorChar(context);
-                if (clientId.matches(baseClientId + separator+"[0-9]+"+separator+".*"))
+                
+                ValueExpression rowKeyVE = getValueExpression("rowKey");
+                boolean rowKeyFound = false;
+                
+                if (rowKeyVE != null)
+                {
+                    int oldRow = this.getRowIndex();
+                    try
+                    {
+                        // iterate over the rows
+                        int rowsToProcess = getRows();
+                        // if getRows() returns 0, all rows have to be processed
+                        if (rowsToProcess == 0)
+                        {
+                            rowsToProcess = getRowCount();
+                        }
+                        int rowIndex = getFirst();
+                        for (int rowsProcessed = 0; rowsProcessed < rowsToProcess; rowsProcessed++, rowIndex++)
+                        {
+                            setRowIndex(rowIndex);
+                            if (!isRowAvailable())
+                            {
+                                break;
+                            }
+                            
+                            if (clientId.startsWith(getContainerClientId(context)))
+                            {
+                                rowKeyFound = true;
+                                break;
+                            }
+                        }
+                        
+                        if (rowKeyFound)
+                        {
+                            returnValue = invokeOnComponentTraverseRow(context, clientId, callback);
+                        }
+                    }
+                    finally
+                    {
+                        this.setRowIndex(oldRow);
+                    }
+                }
+                if (rowKeyVE == null && clientId.matches(baseClientId + separator+"[0-9]+"+separator+".*"))
                 {
                     String subId = clientId.substring(baseClientId.length() + 1);
                     String clientRow = subId.substring(0, subId.indexOf(separator));
@@ -266,31 +308,7 @@ public abstract class AbstractHtmlDataTa
                             return false;
                         }
             
-                        for (Iterator<UIComponent> it1 = getChildren().iterator(); 
-                                !returnValue && it1.hasNext();)
-                        {
-                            //recursive call to find the component
-                            returnValue = it1.next().invokeOnComponent(context, clientId, callback);
-                        }
-                        
-                        if (!returnValue)
-                        {
-                            UIComponent detailStampRowFacet = getFacet(DETAIL_STAMP_ROW_FACET_NAME);
-                            if (detailStampRowFacet != null)
-                            {
-                                returnValue = detailStampRowFacet.invokeOnComponent(context, clientId, callback);
-                            }
-                            UIComponent detailStampFacet = getFacet(DETAIL_STAMP_FACET_NAME);
-                            if (detailStampFacet != null)
-                            {
-                                returnValue = detailStampFacet.invokeOnComponent(context, clientId, callback);
-                            }
-                            UIComponent tableRowFacet = getFacet(TABLE_ROW_FACET_NAME);
-                            if (tableRowFacet != null)
-                            {
-                                returnValue = tableRowFacet.invokeOnComponent(context, clientId, callback);
-                            }
-                        }
+                        returnValue = invokeOnComponentTraverseRow(context, clientId, callback);
                     }
                     finally
                     {
@@ -299,7 +317,7 @@ public abstract class AbstractHtmlDataTa
                         this.setRowIndex(oldRow);
                     }
                 }
-                else
+                else if (!rowKeyFound) //If rowKeyVE == null --> rowKeyFound = false
                 {
                     // MYFACES-2370: search the component in the childrens' facets too.
                     // We have to check the childrens' facets here, because in MyFaces
@@ -343,6 +361,38 @@ public abstract class AbstractHtmlDataTa
         return returnValue;
     }
     
+    private boolean invokeOnComponentTraverseRow(FacesContext context, String clientId,
+            ContextCallback callback)
+    {
+        boolean returnValue = false;
+        for (Iterator<UIComponent> it1 = getChildren().iterator(); 
+            !returnValue && it1.hasNext();)
+        {
+            //recursive call to find the component
+            returnValue = it1.next().invokeOnComponent(context, clientId, callback);
+        }
+
+        if (!returnValue)
+        {
+            UIComponent detailStampRowFacet = getFacet(DETAIL_STAMP_ROW_FACET_NAME);
+            if (detailStampRowFacet != null)
+            {
+                returnValue = detailStampRowFacet.invokeOnComponent(context, clientId, callback);
+            }
+            UIComponent detailStampFacet = getFacet(DETAIL_STAMP_FACET_NAME);
+            if (detailStampFacet != null)
+            {
+                returnValue = detailStampFacet.invokeOnComponent(context, clientId, callback);
+            }
+            UIComponent tableRowFacet = getFacet(TABLE_ROW_FACET_NAME);
+            if (tableRowFacet != null)
+            {
+                returnValue = tableRowFacet.invokeOnComponent(context, clientId, callback);
+            }
+        }
+        return returnValue;
+    }
+    
     public boolean visitTree(VisitContext context, VisitCallback callback)
     {
         if (!isVisitable(context))
@@ -1917,7 +1967,7 @@ public abstract class AbstractHtmlDataTa
 
     public boolean isCurrentDetailExpanded()
     {
-        Boolean expanded = (Boolean) _expandedNodes.get(new Integer(getRowIndex()));
+        Boolean expanded = (Boolean) _expandedNodes.get(getContainerClientId(getFacesContext()));
         if (expanded != null)
         {
             return expanded.booleanValue();
@@ -1983,7 +2033,7 @@ public abstract class AbstractHtmlDataTa
      */
     public void toggleDetail()
     {
-        Integer rowIndex = new Integer(getRowIndex());
+        String derivedRowKey = getContainerClientId(getFacesContext());
 
         // get the current expanded state of the row
         boolean expanded = isDetailExpanded();
@@ -1994,12 +2044,12 @@ public abstract class AbstractHtmlDataTa
             if (isDetailStampExpandedDefault())
             {
                 // if default is expanded we have to override with FALSE here
-                _expandedNodes.put(rowIndex, Boolean.FALSE);
+                _expandedNodes.put(derivedRowKey, Boolean.FALSE);
             }
             else
             {
                 // if default is collapsed we can fallback to this default
-                _expandedNodes.remove(rowIndex);
+                _expandedNodes.remove(derivedRowKey);
             }
         }
         else
@@ -2009,12 +2059,12 @@ public abstract class AbstractHtmlDataTa
             if (isDetailStampExpandedDefault())
             {
                 // if default is expanded we can fallback to this default
-                _expandedNodes.remove(rowIndex);
+                _expandedNodes.remove(derivedRowKey);
             }
             else
             {
                 // if default is collapsed we have to override with TRUE
-                _expandedNodes.put(rowIndex, Boolean.TRUE);
+                _expandedNodes.put(derivedRowKey, Boolean.TRUE);
             }
         }
     }
@@ -2026,9 +2076,7 @@ public abstract class AbstractHtmlDataTa
      */
     public boolean isDetailExpanded()
     {
-        Integer rowIndex = new Integer(getRowIndex());
-
-        Boolean expanded = (Boolean) _expandedNodes.get(rowIndex);
+        Boolean expanded = (Boolean) _expandedNodes.get(getContainerClientId(getFacesContext()));
         if (expanded == null)
         {
             return isDetailStampExpandedDefault();
@@ -2096,9 +2144,29 @@ public abstract class AbstractHtmlDataTa
         int rowCount = getRowCount();
 
         _expandedNodes.clear();
-        for (int row = 0; row < rowCount; row++)
+        
+        if (getRowKey() != null)
         {
-            _expandedNodes.put(new Integer(row), Boolean.TRUE);
+            int oldRow = getRowIndex();
+            try
+            {
+                for (int row = 0; row < rowCount; row++)
+                {
+                    setRowIndex(row);
+                    _expandedNodes.put(getContainerClientId(getFacesContext()), Boolean.TRUE);
+                }
+            }
+            finally
+            {
+                setRowIndex(oldRow);
+            }
+        }
+        else
+        {
+            for (int row = 0; row < rowCount; row++)
+            {
+                _expandedNodes.put(new Integer(row).toString(), Boolean.TRUE);
+            }
         }
     }
 

Modified: myfaces/tomahawk/trunk/core20/src/main/java/org/apache/myfaces/component/html/ext/HtmlDataTableHack.java
URL: http://svn.apache.org/viewvc/myfaces/tomahawk/trunk/core20/src/main/java/org/apache/myfaces/component/html/ext/HtmlDataTableHack.java?rev=1082312&r1=1082311&r2=1082312&view=diff
==============================================================================
--- myfaces/tomahawk/trunk/core20/src/main/java/org/apache/myfaces/component/html/ext/HtmlDataTableHack.java (original)
+++ myfaces/tomahawk/trunk/core20/src/main/java/org/apache/myfaces/component/html/ext/HtmlDataTableHack.java Wed Mar 16 21:39:29 2011
@@ -88,6 +88,8 @@ public abstract class HtmlDataTableHack 
     private static final Class OBJECT_ARRAY_CLASS = (new Object[0]).getClass();
 
     private static final boolean DEFAULT_PRESERVEROWSTATES = false;
+    
+    private static final String UNIQUE_ROW_ID_PREFIX = "r_id_";
 
     private int _rowIndex = -1;
 
@@ -162,14 +164,17 @@ public abstract class HtmlDataTableHack 
         if(index != -1)
         {
             String rowIndexString = clientId.substring(index + 1);
+            
             if(rowIndexString.length() > 0 && 
                StringUtils.isNumeric(rowIndexString) &&
                Integer.valueOf(rowIndexString) == rowIndex)
             {
-                return clientId;
+                //return clientId;
+                return clientId.substring(0,index)+getDerivedSubClientId();
             }
         }
-        return clientId + separator + rowIndex;
+        //return clientId + separator + rowIndex;
+        return clientId + separator + getDerivedSubClientId();
     }
 
     /**
@@ -1111,57 +1116,74 @@ public abstract class HtmlDataTableHack 
          setRowIndex(deletedIndex);
         String currentRowStateKey = getContainerClientId(facesContext);
 
-        // copy next rowstate to current row for each row from deleted row onward.
-        int rowCount = getRowCount();
-        if (isPreserveRowComponentState())
+        Object rowKey = getRowKey(); 
+        if (rowKey != null)
         {
-            for (int index = deletedIndex + 1; index < rowCount; ++index)
+            setRowIndex(deletedIndex);
+            if (isPreserveRowComponentState())
             {
-                setRowIndex(index);
-                String nextRowStateKey = getContainerClientId(facesContext);
-    
-                Map<String, Object> nextRowState = _rowDeltaStates.get(nextRowStateKey);
-                if (nextRowState == null)
-                {
-                    _rowDeltaStates.remove(currentRowStateKey);
-                }
-                else
-                {
-                    _rowDeltaStates.put(currentRowStateKey, nextRowState);
-                }
-                currentRowStateKey = nextRowStateKey;
+                _rowDeltaStates.remove(currentRowStateKey);
+            }
+            else
+            {
+                _rowStates.remove(currentRowStateKey);
             }
-
-            // restore saved row index
             setRowIndex(savedRowIndex);
-    
-            // Remove last row
-            _rowDeltaStates.remove(currentRowStateKey);
         }
         else
         {
-            for (int index = deletedIndex + 1; index < rowCount; ++index)
+            // copy next rowstate to current row for each row from deleted row onward.
+            int rowCount = getRowCount();
+            if (isPreserveRowComponentState())
             {
-                setRowIndex(index);
-                String nextRowStateKey = getContainerClientId(facesContext);
-    
-                Object nextRowState = _rowStates.get(nextRowStateKey);
-                if (nextRowState == null)
+                for (int index = deletedIndex + 1; index < rowCount; ++index)
                 {
-                    _rowStates.remove(currentRowStateKey);
+                    setRowIndex(index);
+                    String nextRowStateKey = getContainerClientId(facesContext);
+        
+                    Map<String, Object> nextRowState = _rowDeltaStates.get(nextRowStateKey);
+                    if (nextRowState == null)
+                    {
+                        _rowDeltaStates.remove(currentRowStateKey);
+                    }
+                    else
+                    {
+                        _rowDeltaStates.put(currentRowStateKey, nextRowState);
+                    }
+                    currentRowStateKey = nextRowStateKey;
                 }
-                else
+    
+                // restore saved row index
+                setRowIndex(savedRowIndex);
+        
+                // Remove last row
+                _rowDeltaStates.remove(currentRowStateKey);
+            }
+            else
+            {
+                for (int index = deletedIndex + 1; index < rowCount; ++index)
                 {
-                    _rowStates.put(currentRowStateKey, nextRowState);
+                    setRowIndex(index);
+                    String nextRowStateKey = getContainerClientId(facesContext);
+        
+                    Object nextRowState = _rowStates.get(nextRowStateKey);
+                    if (nextRowState == null)
+                    {
+                        _rowStates.remove(currentRowStateKey);
+                    }
+                    else
+                    {
+                        _rowStates.put(currentRowStateKey, nextRowState);
+                    }
+                    currentRowStateKey = nextRowStateKey;
                 }
-                currentRowStateKey = nextRowStateKey;
-            }
-
-            // restore saved row index
-            setRowIndex(savedRowIndex);
     
-            // Remove last row
-            _rowStates.remove(currentRowStateKey);
+                // restore saved row index
+                setRowIndex(savedRowIndex);
+        
+                // Remove last row
+                _rowStates.remove(currentRowStateKey);
+            }
         }
     }
     
@@ -1188,13 +1210,80 @@ public abstract class HtmlDataTableHack 
     public void setPreserveRowComponentState(boolean preserveComponentState)
     {
         getStateHelper().put(PropertyKeys.preserveRowComponentState, preserveComponentState);
-    }    
+    }
+    
+    /**
+     * Used to assign a value expression that identify in a unique way a row. This value
+     * will be used later instead of rowIndex as a key to be appended to the container 
+     * client id using getDerivedSubClientId() method.  
+     * 
+     * @return
+     */
+    @JSFProperty
+    public Object getRowKey()
+    {
+        return getStateHelper().eval(PropertyKeys.rowKey);
+    }
+    
+    public void setRowKey(Object rowKey)
+    {
+        getStateHelper().put(PropertyKeys.rowKey, rowKey);
+    }
+    
+    /**
+     * This attribute is used to append an unique prefix when rowKey is not used, to prevent
+     * a key match a existing component id (note two different components can't have the
+     * same unique id).
+     * 
+     * @return
+     */
+    @JSFProperty(defaultValue="r_id_")
+    public String getDerivedRowKeyPrefix()
+    {
+        return (String) getStateHelper().eval(PropertyKeys.derivedRowKeyPrefix, UNIQUE_ROW_ID_PREFIX);
+    }
     
+    public void setDerivedRowKeyPrefix(String derivedRowKeyPrefix)
+    {
+        getStateHelper().put(PropertyKeys.derivedRowKeyPrefix, derivedRowKeyPrefix);
+    }
+
+    /**
+     * Return the fragment to be used on the container client id to
+     * identify a row. As a side effect, it will be used to indicate 
+     * a row component state and a datamodel in nested datatable case.
+     * 
+     * <p>
+     * The returned value must comply with the following rules:
+     * </p>
+     * <ul>
+     * <li> Can be followed by: letters (A-Za-z), digits (0-9), hyphens ("-"), 
+     *   underscores ("_"), colons (":"), and periods (".") </li>
+     * <li> Values are case-sensitive </li>
+     * </ul>
+     * 
+     * @return
+     */
+    protected String getDerivedSubClientId()
+    {
+        Object key = getRowKey();
+        if (key == null)
+        {
+            return _SubIdConverter.encode(Integer.toString(getRowIndex()));
+        }
+        else
+        {
+            return getDerivedRowKeyPrefix() + _SubIdConverter.encode(key.toString());
+        }
+    }
+
     protected enum PropertyKeys
     {
         preserveRowStates
         , forceId
         , forceIdIndex
         , preserveRowComponentState
+        , rowKey
+        , derivedRowKeyPrefix
     }
 }

Added: myfaces/tomahawk/trunk/core20/src/main/java/org/apache/myfaces/component/html/ext/_SubIdConverter.java
URL: http://svn.apache.org/viewvc/myfaces/tomahawk/trunk/core20/src/main/java/org/apache/myfaces/component/html/ext/_SubIdConverter.java?rev=1082312&view=auto
==============================================================================
--- myfaces/tomahawk/trunk/core20/src/main/java/org/apache/myfaces/component/html/ext/_SubIdConverter.java (added)
+++ myfaces/tomahawk/trunk/core20/src/main/java/org/apache/myfaces/component/html/ext/_SubIdConverter.java Wed Mar 16 21:39:29 2011
@@ -0,0 +1,169 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.myfaces.component.html.ext;
+
+import javax.faces.FacesException;
+
+class _SubIdConverter
+{
+    private static final String HEX_CHARSET = "0123456789ABCDEF";
+    
+    /**
+     * Encode the string into an html sub id valid value.  
+     * 
+     * An html id must comply with the following rules
+     * 
+     * 1. Must begin with a letter A-Z or a-z
+     * 2. Can be followed by: letters (A-Za-z), digits (0-9), hyphens ("-"), underscores ("_"), colons (":"), and periods (".")
+     * 3. Values are case-sensitive
+     * 
+     * The first rule is warranted because this convert an sub id, so a prefix is always added to
+     * the returning string. The encoder converts all non valid chars into a unicode hex string prefixed with '_' char.
+     * For example _ is converted to _005F, + is converted to _002B and so on.
+     * 
+     * @param string
+     * @param characterEncoding
+     * @return
+     */
+    public static String encode(final String string)
+    {
+        StringBuilder sb = null;    //create later on demand
+        String app;
+        char c;
+        boolean endLoop = false;
+        for (int i = 0; i < string.length (); ++i)
+        {
+            app = null;
+            c = string.charAt(i);
+            
+            if (( c >= '0' && c <='9') || (c >='A' && c <='Z') || (c >='a' && c <='z')
+                || c == ':' || c == '.' ) //|| c == '-' // '-' used to indicate rowIndex
+            {
+                //No encoding, just do nothing, char will be added later.
+            }
+            else
+            {
+                
+                app = "_" + HEX_CHARSET.charAt( ((c >> 0x0C) % 0x10)) +  HEX_CHARSET.charAt( ((c >> 0x8) % 0x10)) + HEX_CHARSET.charAt( ((c >> 0x4) % 0x10)) +HEX_CHARSET.charAt(c % 0x10);
+            }
+                        
+            if (app != null)
+            {
+                if (sb == null)
+                {
+                    sb = new StringBuilder(string.substring(0, i));
+                }
+                sb.append(app);
+            } else {
+                if (sb != null)
+                {
+                    sb.append(c);
+                }
+            }
+            if (endLoop)
+            {
+                break;
+            }
+        }
+        if (sb == null)
+        {
+            return string;
+        }
+        else
+        {
+            return sb.toString();
+        }
+    }
+
+    public static String decode(final String string)
+    {
+        StringBuilder sb = null;    //create later on demand
+        String app;
+        char c;
+        boolean endLoop = false;
+        for (int i = 0; i < string.length (); ++i)
+        {
+            app = null;
+            c = string.charAt(i);
+            
+            if (c == '_')
+            {
+                int value = (toDigit(string.charAt(i+1)) << 0x0C) + (toDigit(string.charAt(i+2)) << 0x08) + (toDigit(string.charAt(i+3)) << 0x04) + toDigit(string.charAt(i+4));
+                
+                if (sb == null)
+                {
+                    sb = new StringBuilder(string.substring(0, i));
+                }
+                i += 4;
+                app = ""+((char)value);
+            }
+            else
+            {
+                //No decoding
+            }
+
+            if (app != null)
+            {
+                if (sb == null)
+                {
+                    sb = new StringBuilder(string.substring(0, i));
+                }
+                sb.append(app);
+            } else {
+                if (sb != null)
+                {
+                    sb.append(c);
+                }
+            }
+            if (endLoop)
+            {
+                break;
+            }
+        }
+        if (sb == null)
+        {
+            return string;
+        }
+        else
+        {
+            return sb.toString();
+        }
+    }
+    
+    /**
+     * Converts a hexadecimal character to an integer.
+     * 
+     * @param ch
+     *            A character to convert to an integer digit
+     * @param index
+     *            The index of the character in the source
+     * @return An integer
+     * @throws DecoderException
+     *             Thrown if ch is an illegal hex character
+     */
+    protected static int toDigit(char ch)
+    {
+        int digit = Character.digit(ch, 16);
+        if (digit == -1)
+        {
+            throw new FacesException("Illegal hexadecimal charcter ");
+        }
+        return digit;
+    }
+}

Modified: myfaces/tomahawk/trunk/core20/src/main/java/org/apache/myfaces/custom/datalist/AbstractHtmlDataList.java
URL: http://svn.apache.org/viewvc/myfaces/tomahawk/trunk/core20/src/main/java/org/apache/myfaces/custom/datalist/AbstractHtmlDataList.java?rev=1082312&r1=1082311&r2=1082312&view=diff
==============================================================================
--- myfaces/tomahawk/trunk/core20/src/main/java/org/apache/myfaces/custom/datalist/AbstractHtmlDataList.java (original)
+++ myfaces/tomahawk/trunk/core20/src/main/java/org/apache/myfaces/custom/datalist/AbstractHtmlDataList.java Wed Mar 16 21:39:29 2011
@@ -22,6 +22,7 @@ import java.util.Collection;
 import java.util.Iterator;
 import java.util.Map;
 
+import javax.el.ValueExpression;
 import javax.faces.FacesException;
 import javax.faces.component.ContextCallback;
 import javax.faces.component.UIComponent;
@@ -268,12 +269,57 @@ public abstract class AbstractHtmlDataLi
                 // Check if the clientId for the component, which we 
                 // are looking for, has a rowIndex attached
                 char separator = UINamingContainer.getSeparatorChar(context);
-                String subId = clientId.substring(baseClientId.length() + 1);
+                ValueExpression rowKeyVE = getValueExpression("rowKey");
+                boolean rowKeyFound = false;
+                
+                if (rowKeyVE != null)
+                {
+                    int oldRow = this.getRowIndex();
+                    try
+                    {
+                        // iterate over the rows
+                        int rowsToProcess = getRows();
+                        // if getRows() returns 0, all rows have to be processed
+                        if (rowsToProcess == 0)
+                        {
+                            rowsToProcess = getRowCount();
+                        }
+                        int rowIndex = getFirst();
+                        for (int rowsProcessed = 0; rowsProcessed < rowsToProcess; rowsProcessed++, rowIndex++)
+                        {
+                            setRowIndex(rowIndex);
+                            if (!isRowAvailable())
+                            {
+                                break;
+                            }
+                            
+                            if (clientId.startsWith(getContainerClientId(context)))
+                            {
+                                rowKeyFound = true;
+                                break;
+                            }
+                        }
+                        
+                        if (rowKeyFound)
+                        {
+                            for (Iterator<UIComponent> it1 = getChildren().iterator(); 
+                                    !returnValue && it1.hasNext();)
+                            {
+                                //recursive call to find the component
+                                returnValue = it1.next().invokeOnComponent(context, clientId, callback);
+                            }
+                        }
+                    }
+                    finally
+                    {
+                        this.setRowIndex(oldRow);
+                    }
+                }
                 //If the char next to baseClientId is the separator one and
                 //the subId matches the regular expression
-                if (clientId.charAt(baseClientId.length()) == separator && 
-                        subId.matches("[0-9]+"+separator+".*"))
+                if (rowKeyVE == null && clientId.matches(baseClientId + separator+"[0-9]+"+separator+".*"))
                 {
+                    String subId = clientId.substring(baseClientId.length() + 1);
                     String clientRow = subId.substring(0, subId.indexOf(separator));
         
                     //Now we save the current position
@@ -434,7 +480,7 @@ public abstract class AbstractHtmlDataLi
     {
         _facesContext = facesContext;
     }
-    
+
     /**
      * A parameter name, under which the rowCount is set in request 
      * scope similar to the var parameter.

Modified: myfaces/tomahawk/trunk/core20/src/test/java/org/apache/myfaces/component/html/ext/HtmlDataTablePreserveRowComponentStateTest.java
URL: http://svn.apache.org/viewvc/myfaces/tomahawk/trunk/core20/src/test/java/org/apache/myfaces/component/html/ext/HtmlDataTablePreserveRowComponentStateTest.java?rev=1082312&r1=1082311&r2=1082312&view=diff
==============================================================================
--- myfaces/tomahawk/trunk/core20/src/test/java/org/apache/myfaces/component/html/ext/HtmlDataTablePreserveRowComponentStateTest.java (original)
+++ myfaces/tomahawk/trunk/core20/src/test/java/org/apache/myfaces/component/html/ext/HtmlDataTablePreserveRowComponentStateTest.java Wed Mar 16 21:39:29 2011
@@ -21,10 +21,15 @@ package org.apache.myfaces.component.htm
 import java.util.ArrayList;
 import java.util.List;
 
+import javax.faces.component.ContextCallback;
 import javax.faces.component.UIColumn;
+import javax.faces.component.UIComponent;
 import javax.faces.component.UIOutput;
 import javax.faces.component.UIPanel;
 import javax.faces.component.UIViewRoot;
+import javax.faces.context.FacesContext;
+
+import junit.framework.Assert;
 
 import org.apache.myfaces.test.base.AbstractJsfTestCase;
 import org.apache.myfaces.test.utils.TestUtils;
@@ -374,5 +379,181 @@ public class HtmlDataTablePreserveRowCom
             assertEquals(model.get(i).getStyle(), detailStampText.getAttributes().get("style"));
         }
         
+    }
+    
+    public void testPreserveRowComponentStateDetailStamp3() throws Exception
+    {
+        List<RowData> model = new ArrayList<RowData>();
+        model.add(new RowData("text1","style1"));
+        model.add(new RowData("text2","style2"));
+        model.add(new RowData("text3","style3"));
+        model.add(new RowData("text4","style4"));
+        
+        //Put on request map to be resolved later
+        request.setAttribute("list", model);
+        
+        UIViewRoot root = facesContext.getViewRoot();
+        HtmlDataTable table = new HtmlDataTable();
+        UIColumn column = new UIColumn();
+        UIPanel detailStampPanel = new UIPanel();
+        UIOutput text = new UIOutput();
+        UIOutput detailStampText = new UIOutput();
+        
+        //This is only required if markInitiaState fix is not used 
+        root.setId("root");
+        table.setId("table");
+        detailStampPanel.setId("detailStamp");
+        column.setId("column");
+        text.setId("text");
+        detailStampText.setId("detailStampText");
+        
+        table.setVar("row");
+        table.setPreserveRowComponentState(true);
+        table.setValueExpression("value", application.
+                getExpressionFactory().createValueExpression(
+                        facesContext.getELContext(),"#{list}",List.class));
+        
+        table.setValueExpression("rowKey", application.
+                getExpressionFactory().createValueExpression(
+                        facesContext.getELContext(),"#{row.text}",String.class));
+        
+        text.setValueExpression("value", application.
+                getExpressionFactory().createValueExpression(
+                        facesContext.getELContext(),"#{row.text}",String.class));
+
+        detailStampText.setValueExpression("value", application.
+                getExpressionFactory().createValueExpression(
+                        facesContext.getELContext(),"#{row.text}",String.class));
+        
+        root.getChildren().add(table);
+        table.getChildren().add(column);
+        table.getFacets().put(AbstractHtmlDataTable.DETAIL_STAMP_FACET_NAME, detailStampPanel);
+        column.getChildren().add(text);
+        detailStampPanel.getChildren().add(detailStampText);
+
+        //Simulate markInitialState call.
+        facesContext.getAttributes().put("javax.faces.view.ViewDeclarationLanguage.IS_BUILDING_INITIAL_STATE", Boolean.TRUE);
+        root.markInitialState();
+        table.markInitialState();
+        detailStampPanel.markInitialState();
+        detailStampText.markInitialState();
+        column.markInitialState();
+        text.markInitialState();
+        facesContext.getAttributes().remove("javax.faces.view.ViewDeclarationLanguage.IS_BUILDING_INITIAL_STATE");
+        
+        //Check the value expressions are working and change the component state 
+        for (int i = 0; i < model.size(); i++)
+        {
+            RowData rowData = model.get(i); 
+            table.setRowIndex(i);
+            assertEquals(rowData.getText(), text.getValue());
+            assertEquals(rowData.getText(), detailStampText.getValue());
+            text.getAttributes().put("style", rowData.getStyle());
+            detailStampText.getAttributes().put("style", rowData.getStyle());
+        }
+        
+        //Reset row index
+        table.setRowIndex(-1);
+
+        //Remove a row
+        table.deleteRowStateForRow(1);
+        model.remove(1);
+
+        //Check the values were not lost
+        for (int i = 0; i < model.size(); i++)
+        {
+            table.setRowIndex(i);
+            assertEquals(model.get(i).getStyle(), text.getAttributes().get("style"));
+            assertEquals(model.get(i).getStyle(), detailStampText.getAttributes().get("style"));
+        }
+        
     }    
+
+    public void testEncodeDecodeSubId() throws Exception
+    {
+        String test = "someKey_+"+((char)6789);
+        String resp = _SubIdConverter.encode(test);
+        String test2 = _SubIdConverter.decode(resp);
+        //System.out.println(resp+" "+Integer.toHexString(6789));
+        assertEquals(test, test2);
+    }
+    
+    public void testPreserveRowComponentStateDetailStamp4() throws Exception
+    {
+        List<RowData> model = new ArrayList<RowData>();
+        model.add(new RowData("text1","style1"));
+        model.add(new RowData("text2","style2"));
+        model.add(new RowData("text3","style3"));
+        model.add(new RowData("text4","style4"));
+        
+        //Put on request map to be resolved later
+        request.setAttribute("list", model);
+        
+        UIViewRoot root = facesContext.getViewRoot();
+        HtmlDataTable table = new HtmlDataTable();
+        UIColumn column = new UIColumn();
+        UIPanel detailStampPanel = new UIPanel();
+        UIOutput text = new UIOutput();
+        UIOutput detailStampText = new UIOutput();
+        
+        //This is only required if markInitiaState fix is not used 
+        root.setId("root");
+        table.setId("table");
+        detailStampPanel.setId("detailStamp");
+        column.setId("column");
+        text.setId("text");
+        detailStampText.setId("detailStampText");
+        
+        table.setVar("row");
+        table.setPreserveRowComponentState(true);
+        table.setValueExpression("value", application.
+                getExpressionFactory().createValueExpression(
+                        facesContext.getELContext(),"#{list}",List.class));
+        
+        table.setValueExpression("rowKey", application.
+                getExpressionFactory().createValueExpression(
+                        facesContext.getELContext(),"#{row.text}",String.class));
+        
+        text.setValueExpression("value", application.
+                getExpressionFactory().createValueExpression(
+                        facesContext.getELContext(),"#{row.text}",String.class));
+
+        detailStampText.setValueExpression("value", application.
+                getExpressionFactory().createValueExpression(
+                        facesContext.getELContext(),"#{row.text}",String.class));
+        
+        root.getChildren().add(table);
+        table.getChildren().add(column);
+        table.getFacets().put(AbstractHtmlDataTable.DETAIL_STAMP_FACET_NAME, detailStampPanel);
+        column.getChildren().add(text);
+        detailStampPanel.getChildren().add(detailStampText);
+
+        //Simulate markInitialState call.
+        facesContext.getAttributes().put("javax.faces.view.ViewDeclarationLanguage.IS_BUILDING_INITIAL_STATE", Boolean.TRUE);
+        root.markInitialState();
+        table.markInitialState();
+        detailStampPanel.markInitialState();
+        detailStampText.markInitialState();
+        column.markInitialState();
+        text.markInitialState();
+        facesContext.getAttributes().remove("javax.faces.view.ViewDeclarationLanguage.IS_BUILDING_INITIAL_STATE");
+        
+        root.invokeOnComponent(facesContext, "table:r_id_text2:text", new ContextCallback()
+        {
+            public void invokeContextCallback(FacesContext context, UIComponent target)
+            {
+                Assert.assertEquals("text2", target.getAttributes().get("value"));
+            }
+        });
+        
+        root.invokeOnComponent(facesContext, "table:r_id_text3:detailStampText", new ContextCallback()
+        {
+            public void invokeContextCallback(FacesContext context, UIComponent target)
+            {
+                Assert.assertEquals("text3", target.getAttributes().get("value"));
+            }
+        });
+        
+    }
+
 }