14 Replies Latest reply on Jul 15, 2008 7:25 PM by 470068

    How to access members of the current row of an af:table component?

    20862
      For some circumstances i have to read and modify the values of members of the current row of an af:table component.

      For testing it I built a backing bean for the page with a property and a getter method which works well. But how can I access the values of the current row and modify them in the getter method.
      I found an example that offers exactly what I need:
      http://abakalidis.blogspot.com/2007/11/adfjsf-getting-current-row-with-code.html
      But I it's overhauled because the compiler throws depricated messages.

      I know the method will start with:
      FacesContext fctx = FacesContext.getCurrentInstance();
      :-)

      Thanks in advance!
        • 1. Re: How to access menbers of the current row of an af:table component?
          438601
          Are you using just backing beans, or a full blown ADFm binding layer sitting on top of ADF BC? As such is your table data bound with ADF BC through the binding layer? If yes the binding layer makes this really easy, and the following code should help:

          DCIteratorBinding iterator = ADFUtils.findIterator("YourIteratorName");
          Row row = iterator.getCurrentRow();
          String someAttr = row.getAttribute("someAttr").toString();
          String someString = "fish";
          row.setAttribute("someAttr", someString);

          ADFUtils can be stollen from the latest JDev demos (StoreFront). The iterator name exists in the binding page def that sits behind your .jspx page. The attribute names also sits there. You must replace the strings above with the exact names from your binding page def file.

          Does this help or are you attempting to do something else?

          CM.
          • 2. Re: How to access menbers of the current row of an af:table component?
            Frank Nimphius-Oracle
            Hi,

            same code works in 11. Just the change comes after the FacesContext.getCurrentInstance(). In JDeveloper 11 JSF 1.2 is used and as such the ValueBinding code has changed to use an expression factory

            ELContext elctx = fc.getELContext();
            ExpressionFactory elFactory = fc.getApplication().getExpressionFactory
            elFactory.createValueBinding(...)

            Frank
            • 3. Re: How to access menbers of the current row of an af:table component?
              20862
              Hello Chriss,

              thank you for your tip. I'll try it another time, because I first have to get the demos to be able to steel the ADFUtils. Where can they be found? In the libs?
              In any case your tip looks like an easy way.
              • 4. Re: How to access menbers of the current row of an af:table component?
                438601
                You can download the StoreFront demo from the following page. Search for the .zip file to download.

                Once you've got it, open the .zip file, and find the ADFUtils.java and JSFUtils.java files, and include them in your ViewController project.

                Otherwise give Frank's suggestion a go. Let us know how you go.

                Regards,

                CM.
                • 5. Re: How to access menbers of the current row of an af:table component?
                  20862
                  Hello Frank,

                  thank you very much for your tip. When trying it, I got stuck in your piece of code. Maybe there's something missing? On line 2 the two braces and the semicolon are missing, right?. And I'm not sure what to do with elFactory.createValueBinding(...) because ExpressionFactory doesn't have a method createValueBinding.

                  Would you please be so kind and write the whole new example? The best would be even in your blog?

                  After writing the first post in this thread I 'googled' another example. It's the one in your blog at http://thepeninsulasedge.com/frank_nimphius/2007/10/07/adf-faces-getting-the-current-selected-table-row-or-table-row-index/ but it's the old style too.
                  • 6. Re: How to access members of the current row of an af:table component?
                    487442
                    Hi,

                    What is the use case exactly and what kind of modification do you have to do? Is it only a display issue or something more complex? If it's only a display issue, a converter might be what you're looking for.

                    As for your specific question, accessing the current row, check the following code:
                    <af:table value="#{someCollectionModel}" var="row">
                      <af:column>
                        <af:outputText value="#{row.someAttribute}"/>
                      </af:column>
                      <af:column>
                        <af:commandButton label="Edit" action="#{controlBean.edit}">
                          <f:setPropertyActionListener value="#{row}" target="#{controlBean.currentPerson}"/>
                        </af:commandButton>
                      </af:column>
                    </af:table>
                    Now let say someCollectionModel is a list of Person instances, then #{row} represents the current Person instance for the table row. So the controlBean's class could have something like this
                    public class PersonManager
                    {
                        private Person currentPerson;
                    
                        public Person getCurrentPerson()
                        {
                            return currentPerson;
                        }
                    
                        public void setCurrentPerson(Person person)
                        {
                            currentPerson = person;
                        }
                    
                        public String edit()
                        {
                            // currentPerson is the Person instance at the same row as the button
                            currentPerson.setName("John Foobar");
                    
                            return null;
                        }
                    }
                    Now let say #{someCollectionModel} contains
                    Person[] persons = {Person("John Doe"), Person("Olivier Lafontaine"), Person("Chris Muir")};
                    Then, if the user click the edit button on the second row then the persons array content will be modified to
                    Person[] persons = {Person("John Doe"), Person("John Foobar"), Person("Chris Muir")};
                    I hope it makes it clearer, or at least not worst,

                    ~ Simon

                    p.s. Note that using a controlBean implementing Map to hack the EL would allow an action looking like #{controlBean[row].edit}, but that's way more advanced. It also raises a design issue. What is better, having an action that's dependent on the presence of an ActionListener (setPropertyActionListener) or using a hack that is hard to understand?
                    • 7. Re: How to access members of the current row of an af:table component?
                      20862
                      Hello Simon,

                      thank you very much. Your code gives me a better understand for the whole thing.

                      I tried to get Frank's suggestion running. Finally I found the following code for the backing bean. It does complete the email address for a read-only af:table of the employee table of the HR example.
                      The only question remaining is, if the first three lines in getColumnEmail() have to be executed every time the method is called or only once if the constructor is called.
                      ...
                       
                         public String getEmail() {
                            email = getColumnEmail();  
                            return email;
                         }
                         
                         private String getColumnEmail() {
                       
                            FacesContext fctx = FacesContext.getCurrentInstance();
                            ELContext elctx = fctx.getELContext();
                            ExpressionFactory elFactory = fctx.getApplication().getExpressionFactory();
                            ValueExpression valueExp = elFactory.createValueExpression(elctx, "#{row.Email}", Object.class);
                       
                            String email = valueExp.getValue(elctx).toString();
                            
                            email = email.toLowerCase();
                            if(!email.contains("@")) {
                               email = email.concat("@email.com");
                            }
                            return(email);
                       
                         }
                      ...
                      • 8. Re: How to access members of the current row of an af:table component?
                        487442
                        Hi,

                        It has to be called for every row. However, for your use case, you should REALLY create a custom converter instead as it's a reversible display issue. UserEmailConverter could be a good name for it. Assuming you don't know how to create a tag since you seem to be starting with JSF. Let say you have the following base class for all your converters:
                        /* Base class for converters and validators */
                        
                        import FacesBeanUtils; // You'll have to create that one
                        
                        import JSFUtils; // You'll have to create that one as well which is not the same as the Oracle one
                        
                        import java.text.MessageFormat;
                        
                        import java.util.Iterator;
                        import java.util.Locale;
                        import java.util.MissingResourceException;
                        import java.util.ResourceBundle;
                        
                        import javax.faces.application.FacesMessage;
                        import javax.faces.component.StateHolder;
                        import javax.faces.component.UIComponent;
                        import javax.faces.context.FacesContext;
                        
                        import org.apache.commons.lang.ObjectUtils;
                        import org.apache.myfaces.trinidad.bean.FacesBean;
                        import org.apache.myfaces.trinidad.bean.PropertyKey;
                        import org.apache.myfaces.trinidad.util.LabeledFacesMessage;
                        
                        /**
                         * @author Simon Lessard
                         */
                        public abstract class FacesMessageSource implements StateHolder
                        {
                            public static final String DETAIL_MESSAGE_ID_SUFFIX = "_detail";
                        
                            public static final String MESSAGE_BUNDLE = "some.package.Messages";
                            
                            private FacesBean _bean;
                            private boolean _transient;
                        
                            protected FacesMessageSource()
                            {
                                _transient = false;
                            }
                        
                            public boolean equals(Object obj)
                            {
                                if (obj == this)
                                {
                                    return true;
                                }
                                else if (obj == null)
                                {
                                    return false;
                                }
                                else if (!obj.getClass().equals(this.getClass()))
                                {
                                    return false;
                                }
                                else
                                {
                                    FacesMessageSource other = (FacesMessageSource) obj;
                                    if (isTransient() != other.isTransient())
                                    {
                                        return false;
                                    }
                                    
                                    FacesBean thisBean = this.getFacesBean();
                                    FacesBean otherBean = other.getFacesBean();
                                    
                                    if (!thisBean.getType().equals(otherBean.getType()))
                                    {
                                        return false;
                                    }
                                    
                                    for (Iterator<PropertyKey> iter = thisBean.getType().keys(); iter.hasNext();)
                                    {
                                        PropertyKey key = iter.next();
                                        if (!ObjectUtils.equals(thisBean.getProperty(key), otherBean.getProperty(key)))
                                        {
                                            return false;
                                        }
                                    }
                                    
                                    return true;
                                }
                            }
                        
                            public FacesBean getFacesBean()
                            {
                                if (_bean == null)
                                {
                                    _bean = FacesBeanUtils.createFacesBean(getType());
                                }
                        
                                return _bean;
                            }
                        
                            public int hashCode()
                            {
                                int hash =  17 ^ Boolean.valueOf(_transient).hashCode();
                                
                                // This hash function should be improved to include the actual values rather than only keys, else the 
                                // dispersion is REALLY bad. Another solution would be to improve the hashCode of the FacesBean itself, 
                                // which I have on my eternal Trinidad TODO list.
                                for (Iterator<PropertyKey> iter = getFacesBean().getType().keys(); iter.hasNext();)
                                {
                                    hash ^= iter.next().hashCode();
                                }
                                
                                return hash;
                            }
                        
                            public boolean isTransient()
                            {
                                return _transient;
                            }
                        
                            public void restoreState(FacesContext context, Object state)
                            {
                                getFacesBean().restoreState(context, state);
                            }
                        
                            public Object saveState(FacesContext context)
                            {
                                return getFacesBean().saveState(context);
                            }
                        
                            public void setTransient(boolean _transient)
                            {
                                this._transient = _transient;
                            }
                            
                            protected boolean getBooleanProperty(PropertyKey key)
                            {
                                return Boolean.TRUE.equals(getProperty(key));
                            }
                            
                            protected String getMessageBundle()
                            {
                                return MESSAGE_BUNDLE;
                            }
                            
                            protected String getMessageResource(FacesContext context, String resourceId)
                            {
                                String resource = getResourceFromBundle(getMessageBundle(context), resourceId);
                                if (resource == null)
                                {
                                    resource = getResourceFromBundle(getResourceBundle(context, getMessageBundle()), resourceId);
                                }
                                
                                return resource == null ? wrapResourceId(resourceId) : resource;
                            }
                        
                            protected Object getProperty(PropertyKey key)
                            {
                                Object value = getFacesBean().getProperty(key);
                                if (value == null)
                                {
                                    value = key.getDefault();
                                }
                        
                                return value;
                            }
                        
                            public abstract FacesBean.Type getType();
                        
                            protected void setBooleanProperty(PropertyKey key, boolean value)
                            {
                                getFacesBean().setProperty(key, value? Boolean.TRUE: Boolean.FALSE);
                            }
                        
                            protected void setProperty(PropertyKey key, Object value)
                            {
                                getFacesBean().setProperty(key, value);
                            }
                            
                            protected FacesMessage getFacesMessage(FacesContext context, UIComponent component, FacesMessage.Severity severity, 
                                                                   String messageId, Object... parameters)
                            {
                                if (messageId == null)
                                {
                                    throw new NullPointerException("messageId");
                                }
                                
                                String summary;
                                String detail;
                                
                                ResourceBundle bundle = getMessageBundle(context);
                                if (bundle == null)
                                {
                                    // Fallback to default
                                    bundle = getResourceBundle(context, getMessageBundle());
                                    summary = getResourceFromBundle(bundle, messageId, parameters);
                                }
                                else
                                {
                                    summary = getResourceFromBundle(bundle, messageId, parameters);
                                    if (summary == null)
                                    {
                                        // Fallback to default
                                        bundle = getResourceBundle(context, getMessageBundle());
                                        summary = getResourceFromBundle(bundle, messageId, parameters);
                                    }
                                }
                                    
                                detail = getResourceFromBundle(bundle, messageId + DETAIL_MESSAGE_ID_SUFFIX, parameters);
                                
                                if (detail == null && summary == null)
                                {
                                    return null;
                                }
                                
                                if (component != null)
                                {
                                    String label = JSFUtils.getComponentLabel(component);
                                    if (label != null)
                                    {
                                        return new LabeledFacesMessage(severity, summary, detail, label);
                                    }
                                }
                                
                                return new FacesMessage(severity, summary, detail);
                            }
                            
                            protected static ResourceBundle getResourceBundle(FacesContext context, String bundleName)
                            {
                                if (bundleName == null)
                                {
                                    return null;
                                }
                                
                                Locale locale = context.getViewRoot().getLocale();
                                
                                ClassLoader loader = Thread.currentThread().getContextClassLoader();
                                
                                return ResourceBundle.getBundle(bundleName, locale, loader);
                            }
                            
                            protected static String getResourceFromBundle(ResourceBundle bundle, String resourceId, Object... params)
                            {
                                if (resourceId == null)
                                {
                                    throw new NullPointerException("resourceId");
                                }
                                
                                if (bundle == null)
                                {
                                    return null;
                                }
                                
                                try
                                {
                                    String resource = bundle.getString(resourceId);
                                    if (params != null)
                                    {
                                        resource = MessageFormat.format(resource, params);
                                    }
                                    
                                    return resource;
                                }
                                catch (MissingResourceException e)
                                {
                                    return null;
                                }
                            }
                            
                            protected static String wrapResourceId(String resourceId)
                            {
                                return "???" + resourceId + "???";
                            }
                            
                            private ResourceBundle getMessageBundle(FacesContext context)
                            {
                                return getResourceBundle(context, context.getApplication().getMessageBundle());
                            }
                        }
                        
                        /* Base class for converters */
                        
                        import somePackage.FacesMessageSource;
                        
                        import javax.faces.component.UIComponent;
                        import javax.faces.context.FacesContext;
                        import javax.faces.convert.Converter;
                        import javax.faces.convert.ConverterException;
                        
                        
                        /**
                         * @author Simon Lessard
                         */
                        public abstract class BaseConverter extends FacesMessageSource implements Converter
                        {
                            public BaseConverter()
                            {
                            }
                            
                            public final Object getAsObject(FacesContext context, UIComponent component, String value)
                            {
                                if (context == null)
                                { // Enforce NullPointerException on null FacesContext instance
                                    throw new NullPointerException("context");
                                }
                                else if (component == null)
                                { // Enforce NullPointerException on null UIComponent instance
                                    throw new NullPointerException("component");
                                }
                                else if (value == null)
                                { // Enforce null object on null submitted value
                                    return null;
                                }
                                else if (value.length() == 0)
                                { // Enforce null object on empty submitted value
                                    return null;
                                }
                                else
                                { // Parse the submitted value
                                    return getAsObjectNotNull(context, component, value);
                                }
                            }
                            
                            public final String getAsString(FacesContext context, UIComponent component, Object value)
                            {
                                if (context == null)
                                { // Enforce NullPointerException on null FacesContext instance
                                    throw new NullPointerException("context");
                                }
                                else if (component == null)
                                { // Enforce NullPointerException on null UIComponent instance
                                    throw new NullPointerException("component");
                                }
                                else if (value == null)
                                { // Enforce empty value on null object instance
                                    return "";
                                }
                                else
                                { // Format the current value
                                    return getAsStringNotNull(context, component, value);
                                }
                            }
                            
                            protected abstract Object getAsObjectNotNull(FacesContext context, UIComponent component, String value);
                            
                            protected abstract String getAsStringNotNull(FacesContext context, UIComponent component, Object value);
                        }
                        Then you can create your own custom converter for your use case:
                        import javax.faces.application.FacesMessage;
                        import javax.faces.component.UIComponent;
                        import javax.faces.context.FacesContext;
                        import javax.faces.convert.ConverterException;
                        
                        import org.apache.myfaces.trinidad.bean.FacesBean;
                        import org.apache.myfaces.trinidad.bean.PropertyKey;
                        
                        
                        /**
                         * @author Simon Lessard
                         */
                        public class UserEmailConverter extends BaseConverter
                        {
                            private static final FacesMessage.Severity ERROR = FacesMessage.SEVERITY_ERROR;
                        
                            private static final String RESOURCE_KEY_BASE = "some.prefix.convert.UserEmail";
                        
                            /**
                             * <p>Standard converter id for this converter.</p>
                             */
                            public static final String CONVERTER_ID = "some.prefix.UserEmailConverter";
                        
                            public static final String CONVERT_MESSAGE_ID = RESOURCE_KEY_BASE + ".CONVERT";
                        
                            public static final char DELIMITER = '@';
                        
                            public static final String DOMAIN = "email.com";
                        
                            public static final FacesBean.Type TYPE = new FacesBean.Type();
                        
                            private static final String SUFFIX = DELIMITER + DOMAIN;
                        
                            static {
                                TYPE.lock();
                            }
                        
                            public UserEmailConverter()
                            {
                            }
                        
                            /**
                             * {@inheritDoc}
                             */
                            @Override
                            protected Object getAsObjectNotNull(FacesContext context, UIComponent component, String value)
                            {
                                // Superclass contract
                                assert context != null;
                                assert component != null;
                                assert value != null && value.length() > 0;
                        
                                int index = value.lastIndexOf(SUFFIX);
                                if (index == 0)
                                {  // The submitted value is only the suffix
                                    throw new ConverterException(getFacesMessage(context, component, ERROR, CONVERT_MESSAGE_ID, value));
                                }
                                else if (index > 0)
                                {
                                    value = value.substring(0, index);
                                }
                        
                                return value;
                            }
                        
                            /**
                             * {@inheritDoc}
                             */
                            @Override
                            protected String getAsStringNotNull(FacesContext context, UIComponent component, Object value)
                            {
                                // Superclass contract
                                assert context != null;
                                assert component != null;
                                assert value != null;
                                
                                String email = value.toString();
                                if (email.indexOf(DELIMITER) == -1)
                                {
                                    email = email + DELIMITER + DOMAIN;
                                }
                        
                                return email;
                            }
                        }
                        After that, you have to register your converter in faces-config.xml
                        <?xml version="1.0" encoding="windows-1252"?>
                        <faces-config xmlns="http://java.sun.com/xml/ns/javaee"
                                      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                                      xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd"
                                      version="1.2">
                          <converter>
                            <converter-id>some.prefix.UserEmail</converter-id>
                            <converter-class>some.package.UserEmailConverter</converter-class>
                          </converter>
                        </faces-config>
                        Finally, in your page:
                        <af:column>
                          <af:outputText value="#{row.Email}">
                            <f:converter converterId="some.prefix.UserEmail"/>
                          </af:outputText>
                        </af:column>
                        Regards,

                        ~ Simon
                        • 9. Re: How to access menbers of the current row of an af:table component?
                          487442
                          Hi again,

                          I have another advice for you. You seem to be trying to learn the whole ADF stack all at the same time. Although I think it's what Oracle expect people to do, it's not what I would recommend at all. Learning everything at once will prevent you from knowing what part is responsible of what, most likely resulting in a bad use of the technology. You should really learn how to use them as independent modules, especially ADF BC4J (model and data access) and JSF (view and controller). You can choose which one to learn first, it's not important.

                          Once you're learned to use both correctly, then and only then start learning the data binding framework. If you had done that, I believe you wouldn't have been so confused now and would understand every lines of the examples correctly.


                          Regards,

                          ~ Simon
                          • 10. Re: How to access menbers of the current row of an af:table component?
                            487442
                            Hi yet again,

                            Also note that my Converter example is not strictly correct as it's not perfectly reversible for a family of value, that is values already containing @email.com. For example, if the model value is slessard@email.com, then

                            getAsString() will return slessard@email.com
                            getAsObject(), using slessard@email.com as submitted value will return slessard

                            For all other cases the contract is respected, that may or not be an issue for you. Generally speaking a converter should have the following properties:

                            1. Idempotency of getAsString, meaning that:
                            String format1 = converter.getAsString(context, component, value);
                            String format2 = converter.getAsString(context, component, value);
                            assert format1.equals(format2);

                            2. Idempotency of getAsObject, meaning that:
                            Object value1 = converter.getAsObject(context, component, submittedValue);
                            Object value2 = converter.getAsObject(context, component, submittedValue);
                            assert value1.equals(value2);

                            3. Reversibility, meaning that for any value:
                            assert value.equals(converter.getAsObject(context, component, converter.getAsString(context, component, value)));

                            The property 3 is the one that isn't completely enforced by my example. However, that property also happen to be less important if the converter is used only for formatting (solely for output components), then it pretty much mean that getAsObject won't ever be called, so reversibility holds no relevance;


                            Regards,

                            ~ Simon
                            • 11. Re: How to access menbers of the current row of an af:table component?
                              20862
                              Hello Simon,

                              thank you for your example and sharing your knowledge and thank you and all contributors for the time you are spending.
                              As I can see a converter would be the right thing in this case because it is more flexible and adaptable. I didn't take it into consideration, because I didn't know about it. I'll take your example and study how to deal with it.

                              Learning ADF are piles of possibilities and problems equally. The highest hurdle is to find out how the structures are and what to use in which case. The examples on OTN are roads to quick theoretical results but leaving them only in a slight way leads to stony country lanes (as in every IDE or framework) and the tech previews add the deep potholes beginners stumble in. So the best one can get are examples, examples and examples, kind of yours, Frank's, Chris's etc. because the documentation isn't ready yet and things are changing from revision to revision. But I'm shure ... things will get better :-)



                              Best regards!
                              • 12. Re: How to access members of the current row of an af:table component?
                                394178
                                Simon, Great Lesson !
                                You should move it from this thread to a blog so many others can benifit from your example.
                                • 13. Re: How to access members of the current row of an af:table component?
                                  487442
                                  Thanks!

                                  And yeah I should really start blogging :)


                                  ~ Simon
                                  • 14. Re: How to access members of the current row of an af:table component?
                                    470068
                                    Hi Simon,

                                    It's very good idea :):)

                                    Kuba