1

I have a JSF (Mojarra) v2.1.11, Primefaces v3.4.2, Java 6 web application using jQuery datepicker. I am using a selector that looks similar to this:

$('#form1\\:dp').datepicker({showOn: 'button', buttonText: "Choose"});

This enables the user to click the button in order to invoke the datepicker. The issue I'm having is that the "button" disappears after the user submits the form. Apparently, the "button" loads only once when the page is initially loaded. I'd like to know how (what the best practice is) to persist the JQuery datepicker "button" between submits/postbacks.

index.xhtml

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:c="http://java.sun.com/jsp/jstl/core"
      xmlns:p="http://primefaces.org/ui">
    <f:view contentType="text/html">
        <h:head>
            <title>testcal - index.xhtml</title>
            <meta charset="utf-8" />
        </h:head>
        <h:body>
            <h:form id="queryForm">
                <f:event type="postValidate" listener="#{testBean.validate}" />
                <p:panel id="queryPanel"  header="Test p:calendar, datepicker, validation, etc..." style="width:100%;">

                    <p:focus context="queryPanel"/>

                    <h:panelGroup id="msgs">
                        <p:message id="lastName_msg"    for="lastName"     style="display:none;"/>
                        <p:message id="birthDate_msg"   for="birthDate"    style="display:none;"/>
                        <p:message id="startDate_msg"   for="startDate"    style="display:none;"/>
                        <p:message id="endDate_msg"      for="endDate"     style="display:none;"/>
                        <p:message id="drpdwn_msg"       for="drpdwn"      style="display:none;"/>
                    </h:panelGroup>
                    <br/>
                    <br/>
                    <h:panelGroup id="querypanelgroup" style="display:inline-block;">
                        <p:focus context="querypanelgroup"/>

                        <h:panelGroup>
                            <h:panelGroup style="text-align:right;vertical-align:middle;display:inline-block;width:150px">
                                <p:outputLabel style="margin-right: 5px;"  value="Last Name:" for="lastName"/>
                            </h:panelGroup>
                            <h:panelGroup style="margin-left: 4px; vertical-align:middle;display:inline-block;width:250px;">
                                <p:inputText
                                    id="lastName"
                                    value="#{testBean.parmMap['lastName']}"
                                    requiredMessage="last name required"
                                    size="95"
                                    maxlength="95"
                                    onfocus="$('#queryForm\\:msgs > div').hide();$('#queryForm\\:msgs > div').eq(0).show();return false;" >
                                </p:inputText>
                            </h:panelGroup>
                        </h:panelGroup>

                        <br/>
                        <br/>

                        <h:panelGroup>
                            <h:panelGroup style="text-align:right;vertical-align:middle;display:inline-block;width:150px">
                                <p:outputLabel style="margin-right: 5px;"  value="Birth Date:" for="birthDate"/>
                            </h:panelGroup>
                            <h:panelGroup style="margin-left: 4px; vertical-align:middle;display:inline-block;width:250px;">
                                <p:inputText
                                    id="birthDate"
                                    value="#{testBean.parmMap['birthDate']}"
                                    requiredMessage="birth date required"
                                    converter="dpConverter"
                                    styleClass="datePicker"
                                    onfocus="$('#queryForm\\:msgs > div').hide();$('#queryForm\\:msgs > div').eq(1).show();$(this).mask('99/99/9999');return false;">
                                    <p:ajax event="change" process="@this" update="@this"/>
                                </p:inputText>
                            </h:panelGroup>
                        </h:panelGroup>

                        <br/>
                        <br/>

                        <h:panelGroup>
                            <h:panelGroup style="text-align:right;vertical-align:middle;display:inline-block;width:150px">
                                <p:outputLabel style="margin-right: 5px;"  value="Start Date:" for="startDate"/>
                            </h:panelGroup>
                            <h:panelGroup style="margin-left: 4px; vertical-align:middle;display:inline-block;width:250px;">
                                <p:inputText
                                    id="startDate"
                                    value="#{testBean.parmMap['startDate']}"
                                    requiredMessage="start date required"
                                    converter="dpConverter"
                                    styleClass="datePicker"
                                    onfocus="$('#queryForm\\:msgs > div').hide();$('#queryForm\\:msgs > div').eq(2).show();$(this).mask('99/99/9999');return false;">
                                    <!-- optional to populate another field with same value...
                                    onchange="$('...hashSymbolHere...queryForm\\:endDate').val($(this).val());">
                                    -->
                                    <p:ajax event="change" process="@this" update="@this"/>
                                </p:inputText>
                            </h:panelGroup>
                        </h:panelGroup>

                        <br/>
                        <br/>

                        <h:panelGroup>
                            <h:panelGroup style="text-align:right;vertical-align:middle;display:inline-block;width:150px">
                                <p:outputLabel for="endDate" value="Enter a date:"/>
                            </h:panelGroup>
                            <h:panelGroup style="margin-left: 4px; vertical-align:middle;display:inline-block;width:250px;">
                                <p:inputText
                                    id="endDate"
                                    value="#{testBean.parmMap['endDate']}"
                                    requiredMessage="endDate required"
                                    converter="dpConverter"
                                    styleClass="datePicker"
                                    onfocus="$('#queryForm\\:msgs > div').hide();$('#queryForm\\:msgs > div').eq(3).show();$(this).mask('99/99/9999');return false;">
                                    <p:ajax event="change" process="@this" update="@this"/>
                                </p:inputText>
                            </h:panelGroup>
                        </h:panelGroup>

                        <br/>
                        <br/>

                        <h:panelGroup>
                            <h:panelGroup style="text-align:right;vertical-align:middle;display:inline-block;width:150px">
                                <p:outputLabel for="drpdwn" value="Choose from drop down selction:">
                                    <h:panelGroup rendered="false"  style="margin-left: 2px;"  id="drpdwnRequired" styleClass="requiredInd">*</h:panelGroup>
                                </p:outputLabel>
                            </h:panelGroup>
                            <h:panelGroup style="margin-left: 4px; vertical-align:middle;display:inline-block;width:250px;">
                                <p:autoComplete
                                    id="drpdwn"
                                    style="overflow: hidden"
                                    maxResults="200"
                                    scrollHeight="150"
                                    dropdown="false"
                                    value="#{testBean.parmMap['drpdwn']}"
                                    completeMethod="#{testBean.drpdwnListComplete}"
                                    var="entry"
                                    itemLabel="#{entry.split(':')[1]}"
                                    itemValue="#{entry.split(':')[0]}"
                                    minQueryLength="1"
                                    forceSelection="true"
                                    onfocus="$('#queryForm\\:msgs > div').hide();$('#queryForm\\:msgs > div').eq(4).show();" >
                                </p:autoComplete>
                            </h:panelGroup>
                        </h:panelGroup>

                        <br/>
                        <br/>

                        <p:commandButton id="submit" value="Submit" oncomplete="applyDatePicker();" type="submit" update="@form" process="@form" action="#{testBean.submitQuery}" style="width:150px;" styleClass="button"/>
                        <p:commandButton value="Reset" update="@form" onclick="location.reload();return true;" process="@this" actionListener="#{testBean.reset}" immediate="true" ajax="false"/>

                    </h:panelGroup>
                </p:panel>
            </h:form>

            <h:outputStylesheet library="styles"    name="query.css"      />
            <h:outputScript      library="primefaces" name="/jquery/jquery.js" />
            <h:outputScript      library="primefaces" name="/jquery/plugins/ui/jquery-ui.custom.js" />
            <h:outputScript     library="primefaces" name="/jquery/plugins/inputmask/maskedinput.js" />
            <h:outputScript      library="js" name="testcal2.js" target="head" />


        </h:body>
    </f:view>
</html> 

TestBean.java

package aaa.bbb.ccc.war;

import java.io.IOException;
import java.io.Serializable;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIForm;
import javax.faces.component.UIInput;
import javax.faces.component.UIPanel;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.faces.event.ComponentSystemEvent;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component("testBean")
@Scope("view")
public class TestBean implements Serializable
{

    private static final List<String> drpdwnListOrig;  
    private static final String[] prfx = {"aaa","bbb","ccc", "ddd", "eee", "fff","ggg","hhh","iii","jjj","kkk","lll","mmm","nnn","ooo","ppp","qqq","rrr","sss","ttt","uuu","vvv","www","xxx","yyy","zzz"};

    //////////////////////////// [static initializer] ///////////////////////////////
    //////////////////////////// [static initializer] ///////////////////////////////
    //////////////////////////// [static initializer] ///////////////////////////////
    static
    {
        //drpdwnListOrig...
        List<String> l = getListOfStrings();
        drpdwnListOrig = Collections.unmodifiableList(null == l ? new ArrayList<String>() : l);
        FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("drpdwnListOrig", drpdwnListOrig);
        List<String> testList = (List<String>) FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("drpdwnListOrig");
    }

    public TestBean()
    {
        parmMap = this.getParmMap();
        FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("parmMap", parmMap);
    }

    public void reset(ActionEvent event)
    {
        LinkedHashMap<String, Object> m = new LinkedHashMap<String, Object>();
        FacesContext.getCurrentInstance().getExternalContext().getSessionMap().remove("parmMap");
        setParmMap(m);
    }

    public String submitQuery()
    {
        FacesContext.getCurrentInstance().getExternalContext().getSessionMap().remove("hitlistData");
        if (this.getParmMap().isEmpty())
        {
            return "";
        }

        return "/page2.xhtml?faces-redirect=true";
    }


    private static LinkedHashMap<String, Object> parmMap;
    public LinkedHashMap<String, Object> getParmMap()
    {
        LinkedHashMap<String, Object> map = (LinkedHashMap<String, Object>) FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("parmMap");

        if (null == map)
        {
            map = new LinkedHashMap<String, Object>();
        }
        return map;
    }

    public void setParmMap(LinkedHashMap<String, Object> map)
    {
        parmMap = map;
        FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("parmMap", parmMap);
    }


    private static SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy");
    public void validate(ComponentSystemEvent e) throws ParseException
    {

        LinkedHashMap parmMap = this.getParmMap();
        UIForm queryForm = (UIForm) e.getComponent();

        UIInput lastName_c = (UIInput) queryForm.findComponent("lastName");
        String lastName = (String) (lastName_c.getValue());
        UIInput birthDate_c = (UIInput) queryForm.findComponent("birthDate");
        birthDate_c.setValid(true);
        String birthDate = (String) birthDate_c.getValue();

        FacesContext fc = FacesContext.getCurrentInstance();

        if (null != lastName && lastName.trim().length() > 0)
        {
            birthDate_c.setRequired(true);

            if (null == birthDate || birthDate.length() < 1)
            {
                birthDate_c.setValid(false);
                fc.addMessage(birthDate_c.getClientId(), new FacesMessage(FacesMessage.SEVERITY_ERROR, "birth date is required", "birth date is required"));
                fc.renderResponse();
            }
            else
            {
                birthDate_c.setValid(true);
            }
        }
        else
        {
            birthDate_c.setValid(true);
            birthDate_c.setRequired(false);
        }
    }

    //////////////////////////// autocomplete ///////////////////////////////    
    //////////////////////////// autocomplete ///////////////////////////////    
    //////////////////////////// autocomplete ///////////////////////////////    
    private static List<String> drpdwnList;

    public static List<String> getDrpdwnList()
    {
        if (null == drpdwnList)
        {
            drpdwnList = (null == drpdwnList ? (List<String>) FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("drpdwnListOrig") : drpdwnListOrig);
        }

        return drpdwnList;
    }
    public void setDrpdwnList(List<String> data) throws IOException
    {
        drpdwnList = data;
    }
    public static List<String> drpdwnListComplete(String s)       //autocomplete "completeMethod"...
    {
        List<String> list = getDrpdwnList();
        List<String> drpdwnSuggestions = new ArrayList<String>();
        for (String ss : list)
        {
            if (ss.toLowerCase().contains(s.toLowerCase()))
            {
                drpdwnSuggestions.add(ss);
            }
        }
        FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("drpdwnSuggestions", drpdwnSuggestions);

        return drpdwnSuggestions;
    }


    //////////////////////////// get listbox values ///////////////////////////////
    //////////////////////////// get listbox values ///////////////////////////////
    //////////////////////////// get listbox values ///////////////////////////////
    public static List<String> getListOfStrings() //(String sql)
    {
        List<String> list = new ArrayList<String>();

        int ii=0;
        String key=null;
        String val=null;
        for (int i=0;i< 3500; i++)
        {

            if (ii > 25)
            {
                ii=0;
            }

            key = (i + "").trim();
            val = (i + prfx[ii]).trim();

            list.add(key + ":" + val);


            ii++;
        }

        return list;

    }    

}

testcal2.js

$(document).ready(function()
{
    applyDatePicker();
});


function applyDatePicker(){

    $('.datePicker').datepicker(
    {
        showOn: 'button', 
        buttonText: "Choose",
        showButtonPanel: true,
        showOptions: {direction: 'up'},
        changeYear: true,
        changeMonth: true,
        yearRange: "c-120:c+0"
    });
}
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
sairn
  • 461
  • 3
  • 24
  • 58
  • do you want to call the `$('#form1\\:dp').datepicker({showOn: 'button', buttonText: "Choose"});` code snippet after `f:ajax render` ? You mean that the datepicker looses style ? or what ? how do you submit the form ? – Daniel Jan 09 '13 at 19:22
  • Hi Daniel - when the page first renders, a datapicker button is next to the input field. When the page is submitted - on "postback" - the selector is apparently no longer active and the button is gone. I need to "persist" the button so the user does not see it disappear after a submit (especially if there is a validation error). Does this make sense? Let me know if I need to further explain. Thx! s – sairn Jan 09 '13 at 20:13
  • Are you using f:ajax to submit ? Otherwise you can call your js cofe in jquery ready function , if you use f:ajax , than look at this http://stackoverflow.com/a/10270872/617373 – Daniel Jan 09 '13 at 20:56
  • Hi Daniel - I'm using the "$(document).ready" function, and I'm using the "p:commandButton" for my "submit" button... I've added the code I'm testing with above... Can you more easily see whereI am causing the datapicker "button" to disappear? (I'm not certain how to implement the suggestion found in the link you provided). Thx! – sairn Jan 09 '13 at 23:13
  • Drop that datepicker code in a function and call that function in the ready of jquery + in onsuccess of your p:commandButton – Daniel Jan 10 '13 at 00:56
  • updated source code examples, in which I have incorporated the fix recommended by Daniel (below) – sairn Jan 11 '13 at 19:26

2 Answers2

2

If I understood you right

place the js code in a function

function applyDatePicker(){
 $('#queryForm\\:dp2').datepicker(
    {
        showOn: 'button',
        buttonText: "Choose"
    });
}

Call it in $(document).ready

$(document).ready(function()
{
   applyDatePicker();
});

And also call it in <p:commandButton

<p:commandButton onsuccess="applyDatePicker();"...
Daniel
  • 36,833
  • 10
  • 119
  • 200
  • Thank you, Daniel. I'll try your suggestions soon and let you know what happens – sairn Jan 10 '13 at 14:29
  • Hi Daniel. Your clear example and BalusC's explanation have helped me to solve the issue. I instituted your solution - the only difference being that I used "oncomplete", rather than "onsuccess" - in order to persist the "button" on partial postbacks, etc (I hope I'm using the terminology correctly - if not, please forgive me) Thanks again! – sairn Jan 11 '13 at 14:52
  • You are welcome. If the `onsuccess` not being reached it means that something went wrong... and you better check it... – Daniel Jan 11 '13 at 18:15
  • HI Daniel - I have used "oncomplete" in the "submit" "p:commandButton" because I'm performing a "validate(ComponentSystemEvent e)" in my controller which can possibly issue a "facesContext.renderResponse" when it detects a validation error. I dont think this is interpreted as a "success". Does this not make sense? (Thanks, again!) – sairn Jan 11 '13 at 19:13
  • success of an ajax submit is not related to logic validation success... so validation failure should not affect success state of ajax submit at all... – Daniel Jan 11 '13 at 20:46
1

The concrete problem is caused because you're updating the whole form by update="@form", causing the jQuery UI-manipulated input element to be re-rendered as well, causing the jQuery UI look'n'feel to be lost. The $(document).ready() is not re-executed on ajax requests and hence the input element remains "plain".

There are basically 2 solutions to this problem:

  1. Do not update the input element itself on ajax response. Update only the parts which really need to be updated. Perhaps it's just only the message?

    update="msgs"
    

    (by the way, those rendered="true" and style="display:none" are very strange)

    You can if necessary specify multiple components spaceseparated.

    update="msgs1 msgs2 msgs3"
    

    You can also just use autoUpdate="true" on message components which you'd like to automatically update on ajax responses. Or if the component in question doesn't support it, wrap it in <p:outputPanel autoUpdate="true">. See also Understanding PrimeFaces process/update and JSF f:ajax execute/render attributes.


  2. Re-execute the jQuery UI script on complete of ajax request. This part is already answered by Daniel. An alternative to onsuccess is to use OmniFaces' <o:onloadScript>. See also JSF/PrimeFaces ajax updates breaks jQuery event listener function bindings.

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Thank you for your help, BalusC! I realized that the "document.ready" was only being performed once, but, didnt quite know how to go about persisting the datepicker/button. I'll attempt to incorporate your and Daniel's suggestions and let you know the outcome. (FYI - the example I provided was "cut down" from a larger app. I was using the strange "msgs" panelgroup to display messages that correspond to field in focus. -I didnt want to show detail messages next to the field (which is the typical implementation) and I didnt want the big list of all messages at the top of the page. – sairn Jan 10 '13 at 14:40
  • `rendered="true"` is the default already, but `display:none` hides it in the client side causing the message to never appear, so that's just plain strange. JSF will render the message when there's actually a message, so the `display:none` makes no sense either. – BalusC Jan 10 '13 at 14:43
  • Hi BalusC - actually any field level "" that corresponds to a validation/etc error will display (the field message tags will kept in this "div"). For now, I only want field-level messages that correspond to the field in focus to display. I should have explained that the "rendered='true'" is just a placeholder. Eventually I want to give the user a option to choose either (A) the field(in focus) , or (B) the "global" "" (in which case I'd set that "placeholder" rendered attribute to "false".............assuming any of this makes sense at all. Thanks, again! – sairn Jan 11 '13 at 14:41
  • Thank you again, BalusC. (BTW - I chose option "2."). Unfortunately, I am not permitted the flexibility of using "Omnifaces". Thanks again! s – sairn Jan 11 '13 at 14:54
  • You're welcome. I expanded option 1 to tell about `autoUpdate`, you might not have been aware of this. I still don't see any reason to update the entire form including the input field in this particular use case. – BalusC Jan 11 '13 at 14:57