Ajax Listener event valueChange seems to be firing onClick instead of onChange

11,623

Ajax Listener event valueChange seems to be firing onClick instead of onChange

That's indeed the default valueChange event which is been used by the radio buttons and checkboxes which are genreated by the respective JSF components. Check the generated HTML source and you'll see that it's hooked to onclick. The reason why JSF does that by default is clear for checkboxes, but at first sight not entirely clear for radio buttons as they cannot be unticked anyway. The real reason is that it's done in order to ensure IE6/7 compatibility as onchange wouldn't be fired on 1st click in that browser.

If you don't care about IE6/7 users (whose distribution is however stongly decreasing lately), then change it to event="change" instead.

<f:ajax event="change" ... />

This way JSF will generate the event handler on onchange instead.


Update: you could use jQuery's .on('change') function binder which will fix the IE6/7 misbehaviour on the change event. Include the following in <h:head> if you're not already using jQuery:

<script src="http://code.jquery.com/jquery-latest.min.js"></script>

and execute this function on load:

$(function() {
    $(':radio').each(function() {
        var handler = this.onclick;
        this.onclick = null;
        $(this).on('change', handler);
    });
});

This will basically for every radio button move the JSF-generated onclick attribute to change event handler which is managed by jQuery so that it works consitent in all browsers, including IE6/7.

We can of course also do it with plain JS, but that would require lot of boilerplate and cross browser sensitive code.

Share:
11,623
Monica Palitang
Author by

Monica Palitang

Updated on June 09, 2022

Comments

  • Monica Palitang
    Monica Palitang about 2 years

    I have a nested list of Questions that I'd like to display. Initially, I'm displaying the Level 1 questions and then subquestions are displayed based on the users answers to the their parent question. All questions have a radio button and some questions have an input box for additional information that is shown when user selects "Yes"

    Here is my JSF code with nested dataTables. Please note that I have pulled out formatting of these questions in order to simply the question on the forum, so these may look "unpretty" if you copy this code into your own environment and run the code:

    <h:dataTable id="questionTable" var="q" value="#{generalQuestionBean2.questions.questions}">
    <h:column><h:panelGroup id="questionGrp">
    #{q.question} <h:selectOneRadio value="#{q.answer}">
    <f:selectItem itemValue="1" itemLabel="Yes"/>
    <f:selectItem itemValue="0" itemLabel="No"/>
    <f:ajax event="valueChange" execute="@form"
        render="questionGrp"
        listener="#{generalQuestionBean2.reset}"/>
    </h:selectOneRadio> <h:inputText value="#{q.addnInfo}"
    rendered="#{q.answer eq '1' and q.field ne 'otherCov'}"></h:inputText>
    
    <h:panelGroup id="questionGrpSubs" rendered="#{q.addnQuestions ne null and q.answer eq '1'}">
    <h:dataTable id="subQuestionTable" var="subq" value="#{q.addnQuestions}">
    <h:column><h:panelGroup id="subQuestionGrp">
    ->#{subq.question} <h:selectOneRadio id="answer" value="#{subq.answer}"> 
     <f:selectItem itemValue="1" itemLabel="Yes"/>
     <f:selectItem itemValue="0" itemLabel="No"/>
     <f:ajax event="valueChange" execute="@form"
        render="subQuestionGrp"
        listener="#{generalQuestionBean2.reset}"/>
    </h:selectOneRadio><h:inputText value="#{subq.addnInfo}"
    rendered="#{subq.answer eq '1' and subq.field ne 'voluntaryComp' and subq.field ne 'uslh'}"></h:inputText>
    
    <h:panelGroup id="questionGrpSubs2" rendered="#{subq.addnQuestions ne null and subq.answer eq '1'}">
    <h:dataTable id="sub2QuestionTable" var="sub2q" value="#{subq.addnQuestions}">
    <h:column><h:panelGroup id="sub2QuestionGrp">
    -->#{sub2q.question} <h:selectOneRadio id="answer" value="#{sub2q.answer}"> 
     <f:selectItem itemValue="1" itemLabel="Yes"/>
     <f:selectItem itemValue="0" itemLabel="No"/>
     <f:ajax event="valueChange" execute="@form"
        render="sub2QuestionGrp"
        listener="#{generalQuestionBean2.reset}"/>
    </h:selectOneRadio><h:inputText value="#{sub2q.addnInfo}"
    rendered="#{sub2q.answer eq '1'}"></h:inputText>
    
    </h:panelGroup></h:column>
    </h:dataTable></h:panelGroup>
    </h:panelGroup></h:column>
    </h:dataTable></h:panelGroup>
    </h:panelGroup></h:column>
    </h:dataTable>
    

    Here is the code for the reset function on the backing bean:

    private void reset(AjaxBehaviorEvent event) {
        FacesContext context = FacesContext.getCurrentInstance();
        String id = event.getComponent().getClientId(context);
        String[] tokens = id.split("[:]+");
        int qId = -1;
        int subqId = -1;
        int sub2qId = -1;
        for (int i = 0; i < tokens.length; i++) {
            if(tokens[i].equals("questionTable")) 
                qId = Integer.parseInt(tokens[i+1]);
            if(tokens[i].equals("subQuestionTable"))
                subqId = Integer.parseInt(tokens[i+1]);
            if(tokens[i].equals("sub2QuestionTable"))
                sub2qId = Integer.parseInt(tokens[i+1]);
    
        }
        Question q = questions.getQuestion(qId);
        Question processQ = q;
        String defaultSubAnswer = getDefaultSubAnswer(q.getField());
        Question subq;
        Question subq2;
        if(subqId > -1) {
            subq = q.getAddnQuestions().get(subqId);
            processQ = subq;
            if(sub2qId > -1) {
                subq2 = subq.getAddnQuestions().get(sub2qId);
                processQ = subq2;
            }
        }
        resetValue(processQ, defaultSubAnswer);
    }
    
    
    private void resetValue(Question q, String defaultSubAnswer) {
        q.setAddnInfo("");
        if(q.getAddnQuestions() != null) {
            for (int i = 0; i < q.getAddnQuestions().size(); i++) {
                Question subq = q.getAddnQuestions().get(i);
                subq.setAnswer(defaultSubAnswer);
                resetValue(subq, defaultSubAnswer);
            }
        }
    }
    

    Here is the problem:

    The ajax event should default to "valueChange". If I click on "Yes" and then "Yes" again, the ajax call should not happen, correct? But it is, as the Additional Info box is clearing out based on the reset function.

    I had originally tried adding a condition to the reset function to check the value of the button that was clicked and only reset the addnInfo value and subquestions if the answer is "0" (No). But this was causing issues with the rendering as the ajax call would render the input box and subquestions to hide and the value would be held onto on the front end, even though they're reset on the backing bean. When they re-rendered to the front in, the value that was held onto shows up instead of the value in the backing bean.

    Another attempt was using a ValueChangeListener for the reset of the values. But this still has the same issue with the value being held onto when re-rendering.

    I have tried 3 different approaches (listed above) and all have failed. I'm open to hearing a solution to any of these or possibly another solution. Keep in mind that formatting limitations by the users leaves me with less options to work with.