Archive for June, 2011

Prevent double-click on HTML form

How many users are still double-clicking when they visit a website? It is not really a problem with links, but it could be with buttons.
Let’s take, for example, a ‘contact us’ form and a submit button which will send the content of the form by email to the website administrator. What happens if a user double-click on the submit button? The form will be submitted twice and in consequence two emails will be sent instead of one.

There are multiple ways to fix this problem. The solution I will explain below is to disable the submit button once it has been pressed. In that way, even if the user double-clicks on it, the form will be submitted only once! 🙂

To disable the submit button once it has been pressed, you simply need to add the following JavaScript code in the onclick attribute of your button tag:

this.disabled=true;this.form.submit();

For example:

<input name="submitButton" id="submitButton" type="submit" value="Submit" onclick="this.disabled=true;this.form.submit();" />

The code above will work fine if you don’t have an onsubmit attribute in your form tag. But, if you have one, you will notice that the function this.form.submit() will not execute the content of the onsubmit attribute. In that case, you need to force the call as shown below:

this.disabled=true;if(this.form.onsubmit()){this.form.submit();}else{this.disabled=false;}

However, it is recommended to move whatever you have in the onsubmit attribute in the button onclick, such as:

this.disabled=true;if(validateForm()){this.form.submit();}else{this.disabled=false;}

NOTE:
If in your code, you are using the following to check if the form has been submitted:

if (isset($_POST['submitButton'])) { echo "Form submitted!"; }

It will need to be replaced by the following:

if ($_SERVER['REQUEST_METHOD'] == "POST") { echo "Form submitted!"; }

, , , , ,

4 Comments

Delete the components holding unwanted state

This is a small addition to a problem discussed on the MyFaces Wiki: http://wiki.apache.org/myfaces/ClearInputComponents

Just in case the page is removed from the Wiki, please see below a copy of the problem description:

Sometimes you want to provide a command component (eg a link or button) that performs some server action, and renders the same page but with completely “fresh” values for all components.

When using command components with the normal immediate setting (false), achieving this is just a matter of clearing the beans that the JSF component value attributes access. Any values entered by the user will have been pushed into these beans as part of the Update Model phase, so the components themselves will not be holding any information about submitted data. The action method associated with the command is then run which resets the model, and when the components render themselves they will draw fresh data from the (reset) beans.

Note that because data is being pushed into the model, the validation phase must run, and therefore any invalid data in the page will cause the action to be skipped, and the page is redisplayed with the validation errors displayed. This is not generally the desired behaviour for a “clear” type operation! The solution is to set attribute immediate=true on the command so that its associated action is invoked before validation is applied to the input components in the same view (see How_The_Immediate_Attribute_Works).

However when using command components with immediate=true, things become more complex. All components will retrieve the raw submitted values submitted by the user, but the immediate command will then run before they can be pushed into the backing beans; the components therefore remember this data. When the (immediate) action causes navigation to another view then this is no problem; these components will be discarded anyway. However if the action method causes JSF to go directly to the render phase ‘of the same view’ [by calling facesContext.renderResponse()], then the components will behave as they do for a validation failure – by displaying the value cached in the component rather than fetching data from the backing bean.

MyFaces gave four solutions to this problem, but the one I used is the following:

Find the parent component of the problem inputs, and call
parentComponent.getChildren().clear();

During the render phase, new instances of these child components will then be created, while other components will not be affected.

This is effectively the same as the above solution, but discards a selected subset of components rather than the UI!ViewRoot.

Obtaining the parent component to discard can be done via binding. Alternatively, the “action listener” form of callback can be used for the command; this is passed an ActionEvent from which the command component that was clicked can be found. A call to “findComponent” can be made on this to locate the desired parent component by id, or other similar solutions.

All of this is good and well but what if you don’t have the component object but only the name of the form? How would you call the clear method then?

This is the reason why I wrote the code below:

/**
 * Return the UIComponent that represents the root of the UIComponent tree.
 * @return the UIComponent that represents the root of the UIComponent tree
 */
public static UIViewRoot getUIViewRoot() {
    FacesContext facesContext = FacesContext.getCurrentInstance();
    return facesContext != null ? facesContext.getViewRoot() : null;
}

/**
 * Search for a component in the UIComponent tree
 * @param parentComponent the parent component
 * @param componentId the component identifier we look for
 * @return the component found
 */
private static UIComponent findComponent(UIComponent parentComponent, String componentId) {
    if (parentComponent != null) {
        if (componentId.equals(parentComponent.getId())) {
            return parentComponent;
        }
        for (UIComponent child : parentComponent.getChildren()) {
            UIComponent component = findComponent(child, componentId);
            if (component != null) {
                return component;
            }
        }
    }
    return null;
}

/**
 * Deletes components holding unwanted state
 * @param componentId the component identifier
 */
public static void deleteComponentsHoldingUnwantedState(UIComponent parentComponent) {
    if (parentComponent != null) {
        parentComponent.getChildren().clear();
    }
}
    
/**
 * Deletes components holding unwanted state
 * @param componentId the component identifier
 */
public static void deleteComponentsHoldingUnwantedState(String componentId) {
    deleteComponentsHoldingUnwantedState(findComponent(getUIViewRoot(), componentId));
}

The last method of the code above will allow you to delete all the components holding unwanted state of a form simply by passing its name.
For example:

deleteComponentsHoldingUnwantedState("myform");

, , ,

No Comments