Customizing Submit Behavior

Why would I want to customize the submit behavior and what does that mean?

Use Cases:

1. You don't like where the submit button is (in the footer) and you want to move it in your form or you want to change the look and feel of the submit button (like use an image for the button).

2. You might want to perform some error checking before allowing the submit to complete.

3. Disable the dialog that appears on submit showing invalid fields.

4. Customize error text that appears below invalid items.

Case #1 - Creating a form button that triggers the submit action.

1. Create a regular form button.

2. In the onClick event of your button object you can place the following code to activate the "real" submit button:

var actionButtons = form.getStageActions();
for(var i=0; i<actionButtons.length; i++){
  if(get(actionButtons, i).getId() === 'S_Submit')
     get(actionButtons, i).activate();
}

Note: The only part of this code that you may have to change is the "S_Submit", this is the ID of the stage action that you want to trigger.  To find the ID you will have to go to the Stages tab, then open the properties for the button you want to trigger.

3. Next, we need to hide the "real" submit button so that the user can't click it.  If you deselect the checkbox on the page properties to not show the stage buttons then the objects are not created and then cannot be activated.  Instead leave the checkbox enabled and hide the stage buttons with the form is loaded.  In the form onShowActionButtons you can hide the stage buttons by using:

var actionButtons = form.getStageActions();
for(var i=0; i<actionButtons.length; i++){
     get(actionButtons, i).setVisible(false);
}

The above code will hide ALL the stage action buttons.  If you just want to hide a single stage action button then you would have to modify the code to look like:

var actionButtons = form.getStageActions();
for(var i=0; i<actionButtons.length; i++){
     if(get(actionButtons, i).getId() === 'S_Submit') {
        get(actionButtons, i).setVisible(false);
     }
}

In this example we check for the ID of the action button and only hide the ones that we want.

Case #2 - Custom Error checking before submit

There are two ways to perform error checking BEFORE the submit operation:

1. If you are implementing a form button that triggers a submit as referenced in Case #1 then you could add the custom error checking to the onClick event of the form button before you execute the stage action.  For example:

//only perform the submit if the condition passes
if(x = 1) {
  var actionButtons = form.getStageActions();
  for(var i=0; i<actionButtons.length; i++){
    if(get(actionButtons, i).getId() === 'S_Submit')
      get(actionButtons, i).activate();
  }
}

Case # 3 - Disable the Invalid Item Dialog

There is no way to disable the invalid item dialog that appears on submit.  The only way to workaround it is to not use the default validity check and to implement your own using javaScript validation.  Let's look at this in greater detail, what is involved in something like this:

1. Can't use the required property of the form items

2. Can't use rules to change required or valid

Since we can't use the built-in tools for this we now have to manage it on our own.  This means that all the validation will be controlled by javaScript that is executed when the form is loaded in the browser.

3. Since required and valid are controlled by javaScript they cannot be enforced when using the REST API

So to bypass the default dialog we need to get in front of it, we can do that by adding code to the validateButtonPressed event which is executed anytime a stage button is triggered.  The code might look something like this:

if(pActionId === 'S_Submit') {
  //do your custom validation, here you can use setValid() or setRequired() if desired
 //if you consider the form to be in an invalid state then return false to cancel the submit action
  if(!app.getSharedData().isFormValid) {
    return false;
  }
}

This technique looks to cancel the submit action if the form is deemed invalid by your criteria.  If the form is considered valid then the submit is allowed to proceed.  In this way the user would never see the invalid items dialog because we never actually trigger the submit action if any of the fields contain invalid data.

Case #4 - Customize the error text that appears below invalid items

There may be times that you want to customize the error text making it more descriptive of what the user needs to do, below is an image of the default text which as of 8.6.2 cannot be customized in the properties dialog:

Implementing a solution that allows custom messages means that you have to use javaScript and abandon the default error checking and you cannot use the built-in property or a rule for setting the field as required.  The approach that I took looks like this:

1. Created an object array to store the fields that I want to be considered required and the custom message to use

2. In the validateButtonPressed event walk all the form items and use the setValid() function to set the custom error text if the field is empty or invalid.  At the same time we have to set a listener in the onItemChange event to clear the error text if the user enters text into the field.

This solution leverages the recursive function posted in the wiki.  The code is placed in the onLoad event:


//add all the fields that you want to be required and their custom error text to the object array
app.getSharedData().requiredMap = new Array();
app.getSharedData().requiredMap.push({id: 'F_SingleLine2', msg: 'Please enter name.'});
app.getSharedData().requiredMap.push({id: 'F_SingleLine3', msg: 'Please enter address.'});
app.getSharedData().requiredMap.push({id: 'F_SingleLine', msg: 'Please enter age.'});

//This function is used for retrieving an item from the requiredMap by ID specified
app.getSharedData().getArrayVal = function(id) {
  var theMsg = "";
  for(var i=0; i<app.getSharedData().requiredMap.length;i++) {
    var tmp = get(app.getSharedData().requiredMap, i);
    if (tmp.id === id) {
      theMsg = tmp.msg;
      break;
    }
  }
 return theMsg;
}

/*
* This is the function where you place the logic that you want to perform on the item that you are currently looking at.
* The recursive function passes the handle to the current item, from which you can then access any of its properties
*/
app.getSharedData().isFormValid = true;
app.getSharedData().processItem = function(item) {
  if(item.getType() === "text") {

    //do your thing
    var theMsg = app.getSharedData().getArrayVal(item.getId());
    if(theMsg !== "") {  //item is a required item
      if(item.getValue() === "" || !item.getBOAttr().getValid()) {
        app.getSharedData().isFormValid = false;
        item.getBOAttr().setValid(false, theMsg);
      }
    }

    var ev = "onItemChange";
    //add an onItemChange Listener - the code within will be called when the fields value changes
    var hndl = item.connectEvent(ev, function(success){
       var v = item.getBOAttr().getValue();
       if(v !== "") {
         item.getBOAttr().setValid(true, "");
       }
   }); //end of dynamic event listener
  } //end if item.getType
}

/*
* Returns true if the current item has children, otherwise false.
*/
app.getSharedData().hasItems = function(containerID) {
    var list = containerID.getChildren();
    if(list.getLength() > 0) {
     return true;
    } else {
        return false;
    }   
}

/*
* Recursive function used for counting form items.
* containerID: UI item (i.e. page or item)
* processItem: the function that contains the work we want to perform on the item we have accessed
*/
app.getSharedData().getItem = function(containerID, processItem) {
      var itemList;
      var pageList;
      var pageCount = 1;
      debugger;
 
      //check to see if the container is a form as it requires different processing
      if(containerID.getType() === "form") {
          pageList = containerID.getPageIds(); //list of the page IDs - not the actual objects!!
          pageCount = pageList.length;
      } else {
      itemList = containerID.getChildren();    
    }

    //need a loop to account for different pages
    for(var p=0; p<pageCount;p++) {
        if(containerID.getType() === "form") {
          itemList = containerID.getPage(get(pageList, p)).getChildren(); //get the page object from the form
        }
   
        //loop all the items
        for(var i=0; i<itemList.getLength(); i++)
        {
          var theItem = itemList.get(i);
          if(app.getSharedData().hasItems(theItem)) {
              //if container go into it...
                app.getSharedData().getItem(theItem, processItem);   
            } else {
                //other wise do something with the item
                if(dojo.isFunction(processItem)) { //make sure that the parameter passed is a function
                  processItem(theItem);        
                }
            }
        }
      }
}

The code is then triggerd from the validateButtonPressed event:

if(pActionId === 'S_Submit') {

  //reset valid flag for multiple submit attempts in one session
  app.getSharedData().isFormValid = true;

  //walk all form items looking for invalid fields
  app.getSharedData().getItem(form, app.getSharedData().processItem);
  if(!app.getSharedData().isFormValid) {
    return false;
  }
}


The end result is something like this: