3

I have a simple script tag with a function that I include at the bottom of the html body. This script simply disables a submit button. I then have a onclick event that calls the function.

I have this code in 5 different pages and it works on 3 of the five.

Here is the code:

<!-- more non-important html -->

<h:commandButton id="buttonToDisable" 
    value="some text"
    action="#{myBean.myBeansAction}" 
    actionListener="#{myBean.myBeansActionListener}" 
    onclick="disableButton()">

    <!-- I also have an f:param in some of these pages but I didn't 
    think that would matter -->

</h:commandButton>

<!--  more non-important html -->

<script>
    function disabledButton() {
        document.getElementById("myForm:buttonToDisable").disabled = 'true';
    }
</script>

Any help would be greatly appreciated. The only differences between the pages that do and don't work is that the action and actionListeners are different types of beans and some have f:params and others don't.

edhedges
  • 2,722
  • 2
  • 28
  • 61

3 Answers3

7

I understand that your concrete problem is that the backing bean's action isn't been invoked once you disable the button? That can be very true. JSF determines the to-be-invoked action based on the presence of the request parameter name associated with the HTML representation of the UICommand component. However, when a HTML input/button element is disabled, then its name=value won't be sent as request parameter at all (and thus JSF won't be able to determine the to-be-invoked action). The onclick attribute is namely invoked right before the form submit request is been sent.

You'd like to disable the button after the form submit request has been sent to the server side. With the code given so far, the only way would be to call button.disabled=true after a timeout of ~50ms.

<h:commandButton ... onclick="setTimeout('document.getElementById(\'' + this.id + '\').disabled=true;', 50);" />

If you're however using JSF 2.0 <f:ajax>, then there's another, more robust and global, way:

function handleDisableButton(data) {
    if (data.source.type != "submit") {
        return;
    }

    switch (data.status) {
        case "begin":
            data.source.disabled = true;
            break;
        case "complete":
            data.source.disabled = false;
            break;
    }    
}

jsf.ajax.addOnEvent(handleDisableButton);

In the cases where it worked for you, the button was most likely an ajax-enabled button. Disabling the button beforehand has no side effects.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • My statement is still in essence VERY true though... you are simply delaying the execution of the disabling javascript by 50 milliseconds to ensure that it occurs **after** the postback. – maple_shaft Jul 31 '12 at 18:46
  • I was referring to *"You cannot guarantee the order in which event listeners are executed in Javascript!"*. – BalusC Jul 31 '12 at 18:47
  • 1
    ... further I would like to add that the OP decided to use a RichFaces commandButton anyway which makes this unnecessary as they have convenience attributes like 'oncomplete` – maple_shaft Jul 31 '12 at 18:49
  • Yes I was referring to the same statement, without using setTimeout how can you ensure the order in which multiple listeners of onclick are executed? – maple_shaft Jul 31 '12 at 18:50
  • 1
    Uh, the OP didn't mention anywhere that s/he is using `setTimeout()`... How does the first statement in your answer make sense when having read OP's question? The invocation order is just the order in which he functions which are attached to the `onclick` (assuming that they're all synchronous, of course). As to the RichFaces approach, yes, I've read that, but using the RF library was nowhere mentioned in the question, so I just ignored it. I could as good have suggested PrimeFaces' `oncomplete`. – BalusC Jul 31 '12 at 18:53
  • 1
    True, the `setTimeout()` doesn't guarantee the invocation order, but I wasn't talking about that at all when commenting on your answer. – BalusC Jul 31 '12 at 18:56
  • I apologize, I was wrong... I was thinking about jQuery events when I wrote this statement, and it is factually untrue for pure Javascript. I removed the incorrect statement from my answer. – maple_shaft Jul 31 '12 at 19:00
6

You should be setting the enabled status of the commandButton component from the server side actionListener after the click event.

<h:commandButton disabled="#{managedBean.someBooleanPropertyThatWasToggledInTheEvent}" />

You can simply set the managed property after invoking your server side event and it will be disabled on the page refresh.

UPDATE:

I just realized that the OP has a completely different issue. Users are impatient and clicking the button and not waiting for the postback. Disabling the command button however will sometimes prevent the server side actions from being invoked.

The correct way to resolve this is to put an absolute position div overlay across the page with a higher z-index than any other element on the page. This will prevent further clicks from occurring on anything but the overlay div. Here is an example:

CSS style

.div-overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: #000;
    filter:alpha(opacity=50);
    -moz-opacity:0.5;
    -khtml-opacity: 0.5;
    opacity: 0.5;
    z-index: 10000;
}

Javascript toggle function utilizing jQuery fadeToggle functionality

function toggleLoadingOverlay() {
  var div = jQuery('.div-overlay');
  div.fadeToggle(150);
}

** JSF commandButton onclick event that will occur once the button is clicked (and assuming that event propogation has not been interrupted)**

<h:commandButton onclick="toggleLoadingOverlay();" action="..." />
maple_shaft
  • 10,435
  • 6
  • 46
  • 74
  • The reason I want this to be on the client side is because I want this to happen beforethe actionListener is executed. I want to make sure the user can't double click or click again because they become impatient if the page loads slowly. – edhedges Jul 31 '12 at 17:54
  • @edhedges AH, I see! I will update my answer, be patient please... – maple_shaft Jul 31 '12 at 18:05
  • I ended up doing [this](http://stackoverflow.com/a/4267501/1165441) and it worked fine. Thank you for your help though. – edhedges Jul 31 '12 at 18:21
  • 1
    Your first statement is actually not true. – BalusC Jul 31 '12 at 18:39
0

I tried the solution suggested by @maple_shaft, but the opaque/toggleFade() div always absorbed keystrokes even when it was opacity=0. Here's my answer:

<script type="text/javascript">
    function overlayToFront()
    {
        document.getElementById("div-overlay").style.zIndex = "10000";
    }
</script>

page:

<... id="form1"> <!-- in the existing code, some component with id="form1" in my case it was a:form -->
    <div id="div-overlay" 
        style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: #000;
            filter:alpha(opacity=0); -moz-opacity:0.0; -khtml-opacity: 0.0; opacity: 0.0;
            z-index: -10000; cursor: wait;">
        <!-- lay out a transparent div in the background.
        Buttons with onclick="overlayToFront();" can prevent double-clicks by covering the whole GUI when this div gets moved to the front
        Currently only used in one place, but any button with reRender="form1" should be able to to this
        -->
    </div>
    <div id="div-base-layer"
        style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: #000;
            filter:alpha(opacity=0); -moz-opacity:0.0; -khtml-opacity: 0.0; opacity: 0.0;
            z-index: -1;" > <!-- rendered "on top of" div-overlay, so cursor does not change when moving off the legacy part of the page -->
    </div>
    <div>
        <!-- this is where the legacy part of the page, the contents of the component with id="form1" goes. it did not have a position: attribute in the original code so z-index does not affect it -->
    </div>
</ ...> <!-- end of tag with id="form1"  -->
PMorganCA
  • 730
  • 6
  • 24