5 Replies Latest reply on Mar 6, 2009 11:30 AM by Guillaume Jeudy

    ListShuttle validation and invokeOnComponent

    Guillaume Jeudy Master

      Hi,

      I'm using Richfaces 3.2.1.GA.

      I'm trying to retrieve submitted values for items in a listShuttle from a JSF validator. I plugged a validator on the rich:listShuttle element. validate() gets called twice: once passing a list of source items, another time passing a list of target items. These items don't hold submitted values as you could have in inputText fields.

      In order to retrieve submitted values from the JSF UI tree I am trying the approach UIComponent.invokeOnComponent() that works fine on a rich:dataTable by the way. More details can be read there:

      http://weblogs.java.net/blog/jhook/archive/2006/02/new_feature_for.html

      Stepping through the code with a rich:listShuttle usecase gives no luck. At first glance it seems like the problem is the lack of rowKeyConverter for a listShuttle. Debugging rich:dataTable reveals that an IntegerConverter is used to convert the rowKey. Does this mean I should implement a rowKeyConverter for org.richfaces.model.ListShuttleRowKey ? If that is the case what should be the logic contained in the converter?

      Here are some code excerpts of UIDataAdaptor.invokeOnComponent() to put you into context.

      int indexOfSecondColon = clientId.indexOf(NamingContainer.SEPARATOR_CHAR, baseId.length());
       String rowKeyString = null;
       if (indexOfSecondColon > 0) {
       rowKeyString = clientId.substring(baseId.length(),
       indexOfSecondColon);
       Converter keyConverter = getRowKeyConverter();
       if (null != keyConverter) {
       try {
       newRowKey = keyConverter.getAsObject(context, this,
       rowKeyString);
       } catch (ConverterException e) {
       // TODO: log error
       }
       }
       }
       if( null != oldRowKey || null != newRowKey){
       captureOrigValue(context);
       setRowKey(newRowKey);
       }
      Iterator<UIComponent> itr = this.getFacetsAndChildren();
       while (itr.hasNext() && !found) {
       found = itr.next().invokeOnComponent(context, clientId,
       callback);
       }
       if( null != oldRowKey || null != newRowKey){
       setRowKey(oldRowKey);
       restoreOrigValue(context);
       }
      


      getRowConverter() always returns null therefore newRowKey is never set and I guess that is the reason why found variable never resolves to true even though the clientId is valid and refers to column in one of the listShuttle rows.

      Forgive the bad indentation, I pasted from EclipseIDE and thats what it gave...

      Any insights appreciated,
      -Guillaume

        • 1. Re: ListShuttle validation and invokeOnComponent
          Nick Belaevski Master

          Guillaume,

          Take a look here: org.richfaces.renderkit.ListShuttleRendererBase.doDecode(FacesContext, UIComponent). I suggest that you implement your use case in another way:

          1. Create validators that will store submitted data in request-scoped bean. This validator should be assigned to children of list shuttle.
          2. Validate stored values by validator assigned to rich:listShuttle.

          • 2. Re: ListShuttle validation and invokeOnComponent
            Guillaume Jeudy Master

            Nick,

            Thanks for your interest, I did a testrun with invokeOnComponent() supplying a custom rowKeyConverter to my listShuttle as follows:

            public class ListShuttleRowKeyConverter implements javax.faces.convert.Converter {
            
             public Object getAsObject(FacesContext context, UIComponent component, String value) {
             if (context == null || component == null) {
             throw new NullPointerException();
             }
            
             if(value == null) {
             return null;
             }
             boolean target = value.startsWith("t");
             if(target) {
             value = value.substring(1);
             }
            
             try {
             int intValue = Integer.parseInt(value);
             return new ListShuttleRowKey(new Integer(intValue), !target);
             } catch (NumberFormatException e) {
             throw new ConverterException("Failed to convert ListShuttleRowKey:" + e.getMessage(), e);
             }
             }
            
             public String getAsString(FacesContext context, UIComponent component, Object value) {
             if (context == null || component == null) {
             throw new NullPointerException();
             }
            
             if(value == null) {
             return null;
             }
            
             return value.toString();
             }
            
            }


            Below code invoked from JSF validator with hard-coded clientId for test purposes.

            final ContextCallback contextCallback = new ContextCallback() {
             public void invokeContextCallback(FacesContext context, UIComponent target) {
             UIOutput uiOutput = (UIOutput)target;
             Object value = uiOutput.getValue(); // this will return the context sensitive converted value
             if(target instanceof UIInput) {
             UIInput uiInput = (UIInput)target;
             Object submittedValue = uiInput.getSubmittedValue(); // this value in the other hand needs to be converted
             // use submittedValue if it exists instead of existing value
             if(submittedValue != null) {
             value = uiInput.getConverter().getAsObject(context, uiInput, (String)submittedValue);
             }
             }
             valueWrapper.set(value);
             };
             };
            
             component.invokeOnComponent(context, "toRelShuttleForm:toRelShuttle:t0:decorate_validFromDt:validFromDt", contextCallback);
            
             Object testValue = valueWrapper.get();


            It seems like i'm able to retrieve submitted values that way! The remaining challenge now is to generate a list of clientIds from the client side that I can later retrieve in the listShuttle validator on form submit and can match an item from source or target list with a given clientId.

            Concerning your proposal, how can I correlate an item with its corresponding submitted data? If I can't achieve that I don't see how I can perform my validation. (The submitted data does not reflect the full contents of the entity which could have been loaded from the database, if it's an edit).

            Thanks!
            Guillaume

            • 3. Re: ListShuttle validation and invokeOnComponent
              Nick Belaevski Master

              Guillaume,

              When the validator of the nested component is invoked, submitted data is passed in. Store it in the list and then validate the whole list by list shuttle validator after all nested components are processed.

              • 4. Re: ListShuttle validation and invokeOnComponent
                Guillaume Jeudy Master

                Ok Nick, well understood, after unsuccessfully trying the javascript approach to read listShuttle state on the client side, and it seems pretty hard to achieve (i'm not a seasoned javascript coder) and since there is no documentation on this and it is also not part of the public API I will have to give your solution a go. I will post results here.

                Thanks,
                -Guillaume

                • 5. Re: ListShuttle validation and invokeOnComponent
                  Guillaume Jeudy Master

                  Nick,

                  here's what I finally came up with, it's a hybrid based on both my invokeOnComponent() approach and your suggestion.

                  I'm using a RowKeyCollector validator applied at the field level. That validator doesn't validate anything it simply builds a map establishing the rowKey to primary entity id. This map is outjected to a Seam event context (same as request scope).

                  As you mentioned the listShuttle validator is called after the RowKeyCollector validator was called for every row in both source and target lists in the listShuttle.

                  I inject the map populated by RowKeyCollector into my listShuttle validator. I can then iterate through the target list, find out the rowKey for a given entity, I then use invokeOnComponent() building a dynamic clientId based on both validator attributes (gives me info such as what is the shuttleId, the field ids) and the rowKey.

                  I decided to use invokeOnComponent() method because it allows me to more easily build a re-usable class that retrieves an arbitrary number of submitted values from the JSF UI Tree and reflexively build an instance of the entity containing the submitted values.