1. #1
    Sencha Premium Member Neilcoder's Avatar
    Join Date
    Apr 2011
    Location
    Ireland
    Posts
    264
    Vote Rating
    0
    Neilcoder is on a distinguished road

      0  

    Arrow Whats the best way to traverse form fields?

    Whats the best way to traverse form fields?


    Hello all,

    I'm attempting to write a function to allow me to cycle through form fields. I had a look at the API for something similar to; FocusTraversalPolicy but the closest I could come to matching that was the idea of traversing the DOM elements which I'm looking into. I also haven't found out yet how to do the equivalent of instanceof, I've been looking at JSNI but it seems like its a little hard to debug, and I'm not sure if this would provide the robustness I'm looking for. (I want to loop through the elements and if this element is a Field then break the loop and set the focus on it).

    The reason I want to write the function is so that for each form element once it has a value entered, (e.g. combobox value is selected) then the next input on the form can be enabled or made visible. I could hardcode this in but I think this would be the path to a lot of maintenance.

    It would be a nice feature to have (if its not already there and I have just overlooked it in the API)

    also, I found out there is apparently getSuperclass() in GWT 2.5 but my hands are tied from moving to 2.5 yet.

    cheers, Neil

    Update;

    I have this so far;

    PHP Code:
      public Widget getNextWidgetOfContainer(Container containerWidget widget){

          
    int widgetIndex container.getWidgetIndex(widget);
          
    widgetIndex++;
          return 
    container.getWidget(widgetIndex);
      } 
    I essentially want to alter this so it returns only form Fields.

  2. #2
    Ext JS Premium Member jadrake75's Avatar
    Join Date
    Sep 2008
    Posts
    108
    Vote Rating
    4
    jadrake75 is on a distinguished road

      0  

    Default


    I think you can add a ValidationHandler to elements that can evaluate whether they are valid or not. If they are set to validateOnBlur( ) these will get called. Could you not listen here and if it has a valid value, in a ScheduledCommand (meaning defered) load/enable your other components?

    This is assuming they are all GXT components of course.

    -Jason
    Stamp Image Bursting Application
    see http://code.google.com/p/stamp-imagebursting/

  3. #3
    Sencha Premium Member Neilcoder's Avatar
    Join Date
    Apr 2011
    Location
    Ireland
    Posts
    264
    Vote Rating
    0
    Neilcoder is on a distinguished road

      0  

    Default


    Thanks jason, I think I see what you're getting at, I have listeners like those below, what I'm looking to write is an easy to used getNextFocus() style method, so that I don't have to hard code the equivalent of getNextFocus.enabled(true);

    PHP Code:
    private <Tvoid addHandlersForEventObservation(final ComboBox<Tcombo, final LabelProvider<TlabelProvider) {
          

      
    combo.addSelectionHandler(new SelectionHandler<T>() {
          @
    Override
          
    public void onSelection(SelectionEvent<Tevent) {
             
              
              
    Component nextWidget getNextComponentOfContainer(ticketTypeContainercombo);
          
              
    Info.display("TicketType Selected"
                      
    nextWidget.getClass().getName());
              
              
    System.out.println(combo.getClass().getName());
              
    System.out.println(nextWidget.getClass().getName());
              
    alert(nextWidget.getClass().getName());


              
    nextWidget.setEnabled(true);
        


      } 

    I think I'm on the write track, haven't go it to work just yet but I have a quite ugly work around that I hope to get to work, by getting the string of the class name and comparing them.


    PHP Code:
    public Component getNextComponentOfContainer(Container containerComponent component){

          
    boolean matchFound false;
          
    int widgetCount =     container.getWidgetCount();
          
    int widgetIndex container.getWidgetIndex(component);
          
    widgetIndex++;
          
    Component nextComponent = (Componentcontainer.getWidget(widgetIndex);      
          
          while(!
    matchFound){
              
              
    String componentClassName component.getClass().getName();
              
    String nextcomponentClassName nextComponent.getClass().getName();
              
              
    String[] componentNameSplit componentClassName.split("\\.");
              
    String[] nextcomponentNameSplit nextcomponentClassName.split("\\.");
              
              
    String componentBaseString componentNameSplit[componentNameSplit.length 2];
              
    String nextComponentBaseString nextcomponentNameSplit[nextcomponentNameSplit.length 2];
              
              
    System.out.println("componentString: " componentBaseString " nextcomponentString:" nextComponentBaseString);
             
              if(
    componentBaseString.matches(nextComponentBaseString)){
                  
                   
                  
                  if(
    nextcomponentNameSplit[componentNameSplit.length 1].matches("FieldLabel")){
                      
    FieldLabel nextFieldLabel = (FieldLabel)nextComponent;    
                      
    nextComponent = (ComponentnextFieldLabel.getWidget();
                      
    widgetIndex++;
                  } else {
                      
    matchFound true;
                      
                  }
                  
              }else{
                  
                  if(
    widgetIndex==widgetCount)break;
                  
                  
    widgetIndex++;
                  
    nextComponent = (Componentcontainer.getWidget(widgetIndex);              
              }
          }
          return 
    nextComponent;
      } 
    Disclaimer: work in progress, does not work yet

    Update;

    I just assigned itemIds to a couple of comboBoxes and FieldLabels and it seems that if I increase the widget count and get the next widget that the the next widget after the first combobox is its own FieldLabel, so when I return the widget of the Field Label it gives me the first comboBox.

    So a something I have to find out is there a specific order in how widgets get assigned widget indices or anything else I should be aware of about widget ordering?

    Update;

    Maybe There's something Invalid with how I'm using casting, so to make my life easier I've made the function more closely couple to the actual layout but make the function less generic. What I noticed that when I'm trying to obtain the widget index its returning -1 so the counting is irellavent.

    PHP Code:
      public Widget getNextComponentOfContainer(VerticalLayoutContainer containerWidget component){

          
    boolean matchFound false;
          
    int widgetCount =     container.getWidgetCount();
          
    int widgetIndex container.getWidgetIndex(component);
          
    widgetIndex++;
          
    Widget nextComponent container.getWidget(widgetIndex);      
          
          while(!
    matchFound){
              
              
    String componentClassName component.getClass().getName();
              
    String nextcomponentClassName nextComponent.getClass().getName();
              
              
    String[] componentNameSplit componentClassName.split("\\.");
              
    String[] nextcomponentNameSplit nextcomponentClassName.split("\\.");
              
              
    String componentBaseString componentNameSplit[componentNameSplit.length 2];
              
    String nextComponentBaseString nextcomponentNameSplit[nextcomponentNameSplit.length 2];
              
              
    System.out.println("componentString: " componentBaseString " " + ((Component)component).getItemId() + " nextcomponentString:" nextComponentBaseString " " + ((Component)nextComponent).getItemId());
             
              if(
    componentBaseString.matches(nextComponentBaseString)){
                  
                   
                  
                  if((
    nextcomponentNameSplit[componentNameSplit.length 1].matches("FieldLabel")) ){
                      
    FieldLabel nextFieldLabel = (FieldLabel)nextComponent;    
                      
    nextComponent = (ComponentnextFieldLabel.getWidget();
                      
    widgetIndex++;
                  } else if (
    component.equals(nextComponent)){
                      
                      
    widgetIndex++;
                      
    nextComponent = (Componentcontainer.getWidget(widgetIndex);              
                      
                  }else {
                      
    matchFound true;
                  }
                  
              }else{
                  
                  if(
    widgetIndex==widgetCount)break;
                  
                  
    widgetIndex++;
                  
    nextComponent = (Componentcontainer.getWidget(widgetIndex);              
              }
          }
          return 
    nextComponent;
      } 
    .
    PHP Code:
        combo.addSelectionHandler(new SelectionHandler<T>() {
          @
    Override
          
    public void onSelection(SelectionEvent<Tevent) {
              
              
    Widget nextComponent getNextComponentOfContainer(ticketTypeContainercombo);
              
              
    Info.display("TicketType Changed"
                      
    nextComponent.getClass().getName());
              
              
    System.out.println(combo.getClass().getName());
              
    System.out.println(nextComponent.getClass().getName());
              
    alert(nextComponent.getClass().getName());
              ((
    Component)nextComponent).setEnabled(true);
             

          }
        }); 

  4. #4
    Sencha - GXT Dev Team
    Join Date
    Feb 2009
    Location
    Minnesota
    Posts
    2,734
    Vote Rating
    90
    Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light

      0  

    Default


    Another thought to consider when trying to traverse the widget hierarchy and find children - there are a few built-in methods that can help, and FormPanelHelper contains some static methods to help look for Fields specifically.

    The HasWidgets interface, implemented by all containers in GWT and GXT, provides access to an iterator, which can be used to find directly-attached children. By recursively seeking children, you can find all descendant widgets (which could be fields, or anything you are after).

    Another tool to help here is that every Widget supports getting its parent - and you can frequently assume (but check with instanceof!) that this parent implements HasWidgets.

    FormPanelHelper in turn accepts HasWidgets and does some of this seeking for you. It is designed to specifically look for both Fields and FieldLabels::
    * getFields
    * getFieldLabels

    and has several methods for reading or modifying those descendant fields:
    * isValid
    * reset


    Widgets are automatically assigned IDs in a somewhat un-predictable manner, since it is automatic. FieldLabels require IDs to be set on their children to enable focusing that field when the field label is clicked, but beyond that and a few other minor use cases, IDs aren't very important in GXT. Unless you want to explicitly set all Widgets to have an ID, I would stay away from IDs as a way to find things, and stick to more Java APIs. Aside from the benefit of saving your sanity when dealing with autogenerated IDs (which are allocated and assigned as needed, and so can vary) - your code can avoid any JSNI and stick to 'real' java.

  5. #5
    Sencha Premium Member Neilcoder's Avatar
    Join Date
    Apr 2011
    Location
    Ireland
    Posts
    264
    Vote Rating
    0
    Neilcoder is on a distinguished road

      0  

    Default


    Thanks! A very helpful posting once again colin!

    Here's what I've come up with to "enable the next field".

    PHP Code:

      public void enableNextField(Field<?activeFieldContainer container){
          
          
    FormPanelHelper myHelper = new FormPanelHelper();      
          List<
    IsField<?>> helperList = myHelper.getFields(container);

          int index = 0;
          
            for ( IsField<?temp  helperList) {
                           
                
    Field<?> myField = (Field<?>)temp;
                
                if(
    temp.isValid(true) && myField.equals(activeField)){
                    
                    
    IsField<?> nextTemp = helperList.get(index + 1);
                    Field<?myNextField = (Field<?>)nextTemp;
                    
                    if(!myNextField.isEnabled()){

                        myNextField.setEnabled(true);
                        myNextField.unmask();
                        break;
                    }
                }
                
                index++;
            }
                  
      }
    (That way if I add fields to the form later on I won't have edit the onChange method as shown below, as it will enable the fields after it)

    PHP Code:
        emailBody.addChangeHandler(new ChangeHandler() {

            @
    Override
            
    public void onChange(ChangeEvent event) {
                
    // TODO Auto-generated method stub
                
    enableNextField(emailBodyticketTypeContainer);
            }

        }); 
    Only thing I don't know/understand yet is how to trigger the change handler on entering text, rather than after clicking or tabbing away the focus. To be more precise It seems if the following field is a textfield I must focus on another element for the handler, but if the following feild is say a text area, it enables the following feild as soon as I type on the current feild. Is there a more suitable to handler tio use?

  6. #6
    Sencha - GXT Dev Team
    Join Date
    Feb 2009
    Location
    Minnesota
    Posts
    2,734
    Vote Rating
    90
    Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light

      0  

    Default


    The DOM event ONCHANGE only occurs when you leave the current input - that is what you are listening for now. The other change event, ValueChangeEvent<V> has the same semantics - it occurs when the user has left the field, and the field has read out the new value of type V.

    What do you want to listen for? Focus/Blur would give you a more precise hint of entering/leaving, without the need for the user to have made a change. OnKeyUp/Down/Press will tell you about each time the user uses the keyboard - you could update the next field when the user started editing the current one. How do you know that the user has finished and is ready to move on?

  7. #7
    Sencha Premium Member Neilcoder's Avatar
    Join Date
    Apr 2011
    Location
    Ireland
    Posts
    264
    Vote Rating
    0
    Neilcoder is on a distinguished road

      0  

    Default


    Quote Originally Posted by Colin Alworth View Post
    What do you want to listen for? Focus/Blur would give you a more precise hint of entering/leaving, without the need for the user to have made a change. OnKeyUp/Down/Press will tell you about each time the user uses the keyboard - you could update the next field when the user started editing the current one. How do you know that the user has finished and is ready to move on?
    All good questions, some fields are sort of blockers (that is a selection has to be made before moving on to the next) other fields are not order dependent. I think for the text fields that OnKeyUp, because I think it would still nudge the user to fill in the form in an order.... Yup, KeyUpHandler does what I need it to.

    There are some fields that are blockers for progressing to the next field, for example I have a comboBox where selecting a value is mandatory. I tried SelectionHandler, change handler, blur handler.... but now I can see that for comboBoxes what I want is collapse handler, as that tells me the user has made a selection.

    Thanks again for the highly valuable help Colin!

  8. #8
    Sencha - GXT Dev Team
    Join Date
    Feb 2009
    Location
    Minnesota
    Posts
    2,734
    Vote Rating
    90
    Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light

      0  

    Default


    On the ComboBox, SelectionEvent should fire just after the CollapseEvent in the case where the value was changed. Additionally, I believe SelectionEvent will fire if the user typed in a value instead of clicking in the combo listview.

  9. #9
    Sencha Premium Member Neilcoder's Avatar
    Join Date
    Apr 2011
    Location
    Ireland
    Posts
    264
    Vote Rating
    0
    Neilcoder is on a distinguished road

      0  

    Default


    Thanks Colin

    A nice to know but not essential (I know this is slightly off thread topic);

    I have my email with validation shown below, using a ChangeHandler. I want on a change to run my enableNextField method, also shown below. Inside the enableNextField I want to know if myField is valid before enabling an emailCC (that's the bit that's nice but not essential).

    so I use myField.validate(); to check the current field is valid, is that right?

    So in theory if the email field is invalid it should skip the next if statement checking for validity. (i just added an extra validate() check at the time of writing this post )

    What actually happens is that I go to type in an invalid email and then click out that it the next field is enabled and unmasked but the first email (the current field) then is marked as invalid.

    What part of the picture do you think I am missing?

    PHP Code:
        RegExValidator myEmailValidator = new RegExValidator("^(\\w+)([-+.][\\w]+)*@(\\w[-\\w]*\\.){1,5}([A-Za-z]){2,4}$""Email");
        final 
    TextField email = new TextField();
        
    email.getValidators().add(myEmailValidator);
        
    email.setEnabled(false);
        
    email.mask();    
        
    FieldLabel emailLabel = new FieldLabel(email"Email");
        
    emailLabel.setLabelWidth(labelWidth);
        
    ticketTypeContainer.add(emailLabel, new VerticalLayoutData(1, -1));    
        
        
    email.addChangeHandler(new ChangeHandler() {

            @
    Override
            
    public void onChange(ChangeEvent event) {
                
    email.validate();
                if(
    email.isValid(true)){
                
    enableNextField(emailticketTypeContainer);
                }
            }

        }); 

    PHP Code:
      public void enableNextField(Field<?activeFieldContainer container){
          
          
    FormPanelHelper myHelper = new FormPanelHelper();      
          List<
    IsField<?>> helperList = myHelper.getFields(container);

          int index = 0;
          
            for ( IsField<?temp  helperList) {
                
                
    Field<?> myField = (Field<?>)temp;

                
    myField.validate();
                if(
    myField.isValid(true) && myField.equals(activeField)){
                    
                    
    IsField<?> nextTemp = helperList.get(index + 1);
                    Field<?myNextField = (Field<?>)nextTemp;
     
                    
                    if(!myNextField.isEnabled()){
                        myNextField.setEnabled(true);
                        myNextField.unmask();
                        break;
                    }
                }
                
                index++;
            }
                  
      }
    Thanks!

  10. #10
    Sencha Premium Member Neilcoder's Avatar
    Join Date
    Apr 2011
    Location
    Ireland
    Posts
    264
    Vote Rating
    0
    Neilcoder is on a distinguished road

      0  

    Default


    I found that the setAutoValidate(); method partly covers what I'm looking for for the email field.

    I have a problem though checking if the textfield is valid;

    I set my field to auto validate, i type in an invalid email address and.. pow! I get a red outline and stop sign showing an invalid email address.

    However when I then focus elsewhere to fire the event and handler (shown below), it determines that the textField is invalid.

    PHP Code:

            email 
    = new TextField();
        
    email.getValidators().add(myEmailValidator);
        
    email.setAutoValidate(true);
        
    email.setEnabled(false);
        
    FieldLabel emailLabel = new FieldLabel(email"Email");
        
    emailLabel.setLabelWidth(labelWidth);
        
    ticketTypeContainer.add(emailLabel, new VerticalLayoutData(1, -1));    
        
        
    email.addChangeHandler(new ChangeHandler() {

            @
    Override
            
    public void onChange(ChangeEvent event) {
                
    boolean isValid email.isValid();
                if(
    isValid){
                    
    enableNextField(emailticketTypeContainer);
                }
            }

        }); 
    So I also tried the following but it seemed to cause the app to hang, until I commented it out.

    PHP Code:



        email
    .addValidHandler(new ValidHandler(){

            @
    Override
            
    public void onValid(ValidEvent event) {
                    
    enableNextField(emailticketTypeContainer);
                            
            }
            
        }); 
    WhatI would like to do ideally to to set the textField to autoValidate() , capture the validation event firing, run the code I need to run inside the on valid method.

    Cheers.

Thread Participants: 2