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:38:07 UTC

svn commit: r1082309 - in /myfaces/tomahawk/trunk/core/src/main/java/org/apache/myfaces/component/html/ext: AbstractHtmlDataTable.java HtmlDataTableHack.java _SubIdConverter.java

Author: lu4242
Date: Wed Mar 16 21:38:06 2011
New Revision: 1082309

URL: http://svn.apache.org/viewvc?rev=1082309&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/core/src/main/java/org/apache/myfaces/component/html/ext/_SubIdConverter.java
Modified:
    myfaces/tomahawk/trunk/core/src/main/java/org/apache/myfaces/component/html/ext/AbstractHtmlDataTable.java
    myfaces/tomahawk/trunk/core/src/main/java/org/apache/myfaces/component/html/ext/HtmlDataTableHack.java

Modified: myfaces/tomahawk/trunk/core/src/main/java/org/apache/myfaces/component/html/ext/AbstractHtmlDataTable.java
URL: http://svn.apache.org/viewvc/myfaces/tomahawk/trunk/core/src/main/java/org/apache/myfaces/component/html/ext/AbstractHtmlDataTable.java?rev=1082309&r1=1082308&r2=1082309&view=diff
==============================================================================
--- myfaces/tomahawk/trunk/core/src/main/java/org/apache/myfaces/component/html/ext/AbstractHtmlDataTable.java (original)
+++ myfaces/tomahawk/trunk/core/src/main/java/org/apache/myfaces/component/html/ext/AbstractHtmlDataTable.java Wed Mar 16 21:38:06 2011
@@ -33,7 +33,6 @@ import javax.faces.component.EditableVal
 import javax.faces.component.NamingContainer;
 import javax.faces.component.UIColumn;
 import javax.faces.component.UIComponent;
-import javax.faces.component.UIComponentBase;
 import javax.faces.context.FacesContext;
 import javax.faces.el.ValueBinding;
 import javax.faces.model.DataModel;
@@ -1485,7 +1484,7 @@ public abstract class AbstractHtmlDataTa
 
     public boolean isCurrentDetailExpanded()
     {
-        Boolean expanded = (Boolean) _expandedNodes.get(new Integer(getRowIndex()));
+        Boolean expanded = (Boolean) _expandedNodes.get(getClientId(getFacesContext()));
         if (expanded != null)
         {
             return expanded.booleanValue();
@@ -1554,7 +1553,7 @@ public abstract class AbstractHtmlDataTa
      */
     public void toggleDetail()
     {
-        Integer rowIndex = new Integer(getRowIndex());
+        String derivedRowKey = getClientId(getFacesContext());
 
         // get the current expanded state of the row
         boolean expanded = isDetailExpanded();
@@ -1565,12 +1564,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
@@ -1580,12 +1579,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);
             }
         }
     }
@@ -1597,9 +1596,7 @@ public abstract class AbstractHtmlDataTa
      */
     public boolean isDetailExpanded()
     {
-        Integer rowIndex = new Integer(getRowIndex());
-
-        Boolean expanded = (Boolean) _expandedNodes.get(rowIndex);
+        Boolean expanded = (Boolean) _expandedNodes.get(getClientId(getFacesContext()));
         if (expanded == null)
         {
             return isDetailStampExpandedDefault();
@@ -1664,9 +1661,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(getClientId(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/core/src/main/java/org/apache/myfaces/component/html/ext/HtmlDataTableHack.java
URL: http://svn.apache.org/viewvc/myfaces/tomahawk/trunk/core/src/main/java/org/apache/myfaces/component/html/ext/HtmlDataTableHack.java?rev=1082309&r1=1082308&r2=1082309&view=diff
==============================================================================
--- myfaces/tomahawk/trunk/core/src/main/java/org/apache/myfaces/component/html/ext/HtmlDataTableHack.java (original)
+++ myfaces/tomahawk/trunk/core/src/main/java/org/apache/myfaces/component/html/ext/HtmlDataTableHack.java Wed Mar 16 21:38:06 2011
@@ -77,6 +77,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;
 
@@ -117,6 +119,7 @@ public abstract class HtmlDataTableHack 
         {
             return clientId;
         }
+        
         // the following code tries to avoid rowindex to be twice in the client id
         int index = clientId.lastIndexOf(NamingContainer.SEPARATOR_CHAR);
         if(index != -1)
@@ -126,15 +129,15 @@ public abstract class HtmlDataTableHack 
             {
                 if(Integer.parseInt(rowIndexString) == rowIndex)
                 {
-                    return clientId;
+                    return clientId.substring(0, index+1) + getDerivedSubClientId();
                 }
             }
             catch(NumberFormatException e)
             {
-                return clientId + NamingContainer.SEPARATOR_CHAR + rowIndex;
+                return clientId + NamingContainer.SEPARATOR_CHAR + getDerivedSubClientId();
             }
         }
-        return clientId + NamingContainer.SEPARATOR_CHAR + rowIndex;
+        return clientId + NamingContainer.SEPARATOR_CHAR + getDerivedSubClientId();
     }
 
     /**
@@ -521,11 +524,12 @@ public abstract class HtmlDataTableHack 
     
     public Object saveState(FacesContext context)
     {
-        Object[] values = new Object[4];
+        Object[] values = new Object[5];
         values[0] = super.saveState(context);
         values[1] = _preserveRowStates;
         values[2] = _forceId;
         values[3] = _forceIdIndex;
+        values[4] = _derivedRowKeyPrefix;
         return values;
     }
     
@@ -536,6 +540,7 @@ public abstract class HtmlDataTableHack 
         _preserveRowStates = (Boolean) values[1];
         _forceId = (Boolean) values[2];
         _forceIdIndex = (Boolean) values[3];
+        _derivedRowKeyPrefix = (String) values[4];
     }
 
     private static final DataModel EMPTY_DATA_MODEL = new _SerializableDataModel()
@@ -682,34 +687,148 @@ public abstract class HtmlDataTableHack 
         // save row index
         int savedRowIndex = getRowIndex();
         
-        FacesContext facesContext = FacesContext.getCurrentInstance();
+        FacesContext facesContext = getFacesContext();
          setRowIndex(deletedIndex);
         String currentRowStateKey = getClientId(facesContext);
 
-        // copy next rowstate to current row for each row from deleted row onward.
-        int rowCount = getRowCount();
-        for (int index = deletedIndex + 1; index < rowCount; ++index)
+        Object rowKey = getRowKey(); 
+        if (rowKey != null)
+        {
+            setRowIndex(deletedIndex);
+            _rowStates.remove(currentRowStateKey);
+            setRowIndex(savedRowIndex);
+        }
+        else
         {
-            setRowIndex(index);
-            String nextRowStateKey = getClientId(facesContext);
+            // copy next rowstate to current row for each row from deleted row onward.
+            int rowCount = getRowCount();
+            for (int index = deletedIndex + 1; index < rowCount; ++index)
+            {
+                setRowIndex(index);
+                String nextRowStateKey = getClientId(facesContext);
+
+                Object nextRowState = _rowStates.get(nextRowStateKey);
+                if (nextRowState == null)
+                {
+                    _rowStates.remove(currentRowStateKey);
+                }
+                else
+                {
+                    _rowStates.put(currentRowStateKey, nextRowState);
+                }
+                currentRowStateKey = nextRowStateKey;
+            }
 
-            Object nextRowState = _rowStates.get(nextRowStateKey);
-            if (nextRowState == null)
+            // Remove last row
+            _rowStates.remove(currentRowStateKey);
+
+            // restore saved row index
+            setRowIndex(savedRowIndex);
+        }
+    }
+    
+    //Since it should be unique, no need to store it as a local var
+    //private Object _rowKey;
+    
+    /**
+     * 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.  
+     *
+     * @JSFProperty
+     * @return
+     */
+    public Object getRowKey()
+    {
+        //if (_rowKey != null)
+        //{
+        //    return _rowKey;
+        //}
+        ValueBinding vb = getValueBinding("rowKey");
+        if (vb != null)
+        {
+            Object value = vb.getValue(getFacesContext());
+            if (value == null)
             {
-                _rowStates.remove(currentRowStateKey);
+                return null;
             }
             else
             {
-                _rowStates.put(currentRowStateKey, nextRowState);
+                return (Object) value;
             }
-            currentRowStateKey = nextRowStateKey;
         }
+        return null;
+    }
+    
+    public void setRowKey(Object rowKey)
+    {
+        //_rowKey = rowKey;
+    }
+    
+    private String _derivedRowKeyPrefix;
+    
+    /**
+     * 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).
+     * 
+     * @JSFProperty defaultValue="r_id_"
+     * @return
+     */
+    public String getDerivedRowKeyPrefix()
+    {
+        if (_derivedRowKeyPrefix != null)
+        {
+            return _derivedRowKeyPrefix;
+        }
+        ValueBinding vb = getValueBinding("derivedRowKeyPrefix");
+        if (vb != null)
+        {
+            Object value = vb.getValue(getFacesContext());
+            if (value == null)
+            {
+                return UNIQUE_ROW_ID_PREFIX;
+            }
+            else
+            {
+                return (String) value.toString();
+            }
+        }
+        return UNIQUE_ROW_ID_PREFIX;
+    }
 
-        // Remove last row
-        _rowStates.remove(currentRowStateKey);
+    public void setDerivedRowKeyPrefix(String derivedRowKeyPrefix)
+    {
+        this._derivedRowKeyPrefix = derivedRowKeyPrefix;
+    }
 
-        // restore saved row index
-        setRowIndex(savedRowIndex);
+    /**
+     * 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());
+        }
     }
-    
+
 }

Added: myfaces/tomahawk/trunk/core/src/main/java/org/apache/myfaces/component/html/ext/_SubIdConverter.java
URL: http://svn.apache.org/viewvc/myfaces/tomahawk/trunk/core/src/main/java/org/apache/myfaces/component/html/ext/_SubIdConverter.java?rev=1082309&view=auto
==============================================================================
--- myfaces/tomahawk/trunk/core/src/main/java/org/apache/myfaces/component/html/ext/_SubIdConverter.java (added)
+++ myfaces/tomahawk/trunk/core/src/main/java/org/apache/myfaces/component/html/ext/_SubIdConverter.java Wed Mar 16 21:38:06 2011
@@ -0,0 +1,170 @@
+/*
+ * 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;
+import java.lang.StringBuffer;
+
+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)
+    {
+        StringBuffer 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 StringBuffer(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)
+    {
+        StringBuffer 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 StringBuffer(string.substring(0, i));
+                }
+                i += 4;
+                app = ""+((char)value);
+            }
+            else
+            {
+                //No decoding
+            }
+
+            if (app != null)
+            {
+                if (sb == null)
+                {
+                    sb = new StringBuffer(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;
+    }
+}