Showing posts with label jsf. Show all posts
Showing posts with label jsf. Show all posts

Sunday, October 27, 2013

Write Once Run Anywhere .... if you can

Let's see. A few weeks back my hard-drive crashed and I needed to replace it. Luckily, I had saved my data previously, among which a Java-based application with 3 tiers: JSF, jQuery, servlets, and an open-source database. I wanted thus to resurrect this application. "Piece of cake" I told to myself, Java is after all WORA (Write Once Run Anywhere).
So this is what I've done:

  1. downloaded and installed Eclipse Juno (which didn't start on double-clicking the exe, but rather using this command line: C:\eclipse\eclipse.exe -vm "C:\Program Files\Java\jre7\bin")

  2. I want to run this application on Apache Tomcat and Glassfish application servers, so after downloading and installing both, I needed to download server adapters, as shown in the following figure (see "Download additional server adapters" in the figure below)

Note that I tried first with Glassfish 4.0.

3. I created a Dynamic Web Project in Eclipse and imported the sources of my project (saved from the previous hard-drive)

4. Then, as I was building the project, I noticed missing required jar files, so I started looking for them (either jarfinder.com or findjar.com) and adding them to \WEB-INF\lib

At this point in time I thought everything should work fine. 
But first I encountered a strange run-time exception, as shown below:

5. After a bit of searching online, it turned out I needed to configure Project Facets to use Dynamic Web Project 3.0, as shown below:

Note: it's very important to select the right Project Facets, because that decides what jars will be used. Fro example, if you don't choose the JSF facet, then you need to add the JSF jars yourself; sometimes there's a good reason to do just that, e.g. the default provided jars won't do the job, so you want to use other jars.

6. As you can see, Eclipse complained about the Java version, so I needed to use the Java 7 compiler, as shown in the following figures:


7. After all this hassle, my application worked, but only with Glassfish 3.1; on 4.0 it throws an NPE; on Tomcat, no way to make it work, even though the same application previously worked ONLY on Tomcat and not on Glassfish ....

By the way, in the past I also had an issue with an application working fine on the former Oracle OAS and not working at all on Tomcat. It turned out that replacing the standard Oracle JSF libs with the ones from Mojarra helped. But you never know ....

I need to investigate this further, but now I'm happy at least I made it work on Glassfish 3.1.

Greetings from the jungle,
    Sorin


P.S. A great article about JSF 2.0 with Glassfish and Eclipse is here: http://balusc.blogspot.nl/2011/01/jsf-20-tutorial-with-eclipse-and.html.


Sunday, June 9, 2013

Sweat, blood and tears with JSF

I'll start with the conclusion: if you're building standard, textbook web applications with Java Server Faces, you're going to love the technology. But the moment you start developing things that are a bit off the beaten track, the fun begins .... Let's take for example the calendar I presented recently. I want to add it some more functionality: when clicking on a certain day, you're redirected to a new page where you can create a new appointment. Once you confirm it, you'll be returned to the calendar page. So, this is how I extended the jQuery calendar:


dayClick: function(date, allDay, jsEvent, view) {
     if (allDay) {
        alert('Clicked on the entire day: ' + date);
        var url = "http://localhost:8080/Patients/appointments.jsf?day=" + date.getDate() + "&month=" + (date.getMonth() + 1) + "&year=" + date.getFullYear();    
        $(location).attr('href',url);  
     } else {
        //alert('Clicked on the slot: ' + date);
     }

}
Note that I've hardcoded the path to the appointment page ("appointments.jsp"). More importantly, I'm passing the date (day, month, year) as URL request parameters, e.g.: http://localhost:8080/Patients/appointments.jsf?day=5&month=7&year=2013

On the appointments.jsp page, one of the things I do is to print the chosen date:
<%
out.println("Data: " + request.getParameter("day") + "-" + request.getParameter("month") + "-" + request.getParameter("year"));
%>
Then, I've added a text input and a button, so that the user can fill in a patient name (or part of it) and click the button in order to search (in a DB) for all patients with the name containing the given text. The patients are supposed to be displayed in a selectOneListBox, as shown below:
<h:inputText binding="#{PatientsSearch.inputText1}" valueChangeListener="#{PatientsSearch.lookForName}" immediate="true" id="inputText1"/>
<h:commandButton value="Patient search:" action="#{PatientsSearch.getPatientsByName}">
</h:commandButton>

<h:selectOneListbox id="patients" value="#{PatientsSearch.selectedPatient}">
<f:selectItems value="#{PatientsSearch.patients}" var="p"
       itemLabel="#{p.name}:#{p.phoneMobile}" itemValue="#{p.name}" />
</h:selectOneListbox>

Almost everything works fine, i.e. if you click the "Patient search" button, the listbox will indeed be filled in with the corresponding patients' names and thus you can select on of the names from the list. I'm saying "almost", because if you change your mind and look up another patient name, clicking the "Patient search" button doesn't produce any result (actually I debugged it and noticed that the handler PatientsSearch.getPatientsByName is indeed called, but somehow the listbox is not refreshed with the new names). If you don't select any name in the list and perform a new patient search, this will work (so clicking the button produces the desired result). It looks as if the listbox is not just a "select one", but "use only once": the moment you select an item from the list, you can't refresh it anymore ....
Anyway, I actually didn't like the list, so I've replaced it with a table:
<h:dataTable var="patient" value="#{PatientsSearch.patients}" style="width:300px;height:150px" rowClasses="oddRow, evenRow">
  <h:column id="name">
    <f:facet name="header">
    <h:outputText value="Name" />
    </f:facet>
    <h:outputText value="#{patient.name}" />
  </h:column>
  <h:column id="phone">
    <f:facet name="header">
    <h:outputText value="Phone" />
    </f:facet>
    <h:outputText value="#{patient.phoneMobile}" />
  </h:column>
  <h:column>
    <h:inputHidden id="patientId" value="#{patient.id}"/>
  </h:column>
</h:dataTable>
I want to show the patient's name in the first column and the patient's phone number in the second. Wait a minute, there are three columns .... The third column contains an inputHidden field, whose value is the patient's ID - patient.id. So, what's the deal with this one ? Well, when I click a row in this table, I want that patient to be selected. Since the patient's ID uniquely identifies each patient, I can't simply use the name (it might not be unique). So I'm going to keep the patient's ID in this hidden field and when the user selects a patient, this ID will be sent to the managed bean that takes care of creating the new appointment using the patient's ID. Compare this with the selectOneListbox above: when I was selecting a patient in the listbox, the selected Patient instance (as a Java type) was passed to the managed bean, as the field PatientsSearch.selectedPatient. So I'm losing this elegance when working with the table, but ok ....

Next, let's do what I've just said: when the user selects a patient from the list, the patient's ID needs to be sent to a backing bean (i.e. we need to preserve it in order to create the appointment later). Again, look how simple was to do that with the selectOneListbox: just add value="#{PatientsSearch.selectedPatient}" and you're done. This is the intrinsic functionality of the listbox. The table however is meant rather for listing data, such as the patients in our case, not necessarily for selecting it. After googling around for a while, I've arrived to the conclusion that all I need to do is:
1. use some jQuery/JavaScript functionality to select a row in the table (and highlight the selection)
2. use a JSF inputHidden field to keep the patient's ID of the selected row

These two steps are shown below. Note that I first reset the background of all "tr" (except for the first, which is the header of the table and thus excluded from any selection) and then I set the background of the selected row to orange.

After that I do step 2 - this single line of code cost me many hours of searches online: $("#j_id_id13\\:testId").val($(this).find('td:last').find('input').attr("value"));
Note that the id of the inputHidden is "testId", but JSF prefixes it with "j_id_id13" when generating the corresponding HTML input field - you can see that by showing the page source in the browser. "\\" are needed to escape ":". Then, $(this).find('td:last') means that I want to go to the last column (td) in the current row (this). Finally, I look for "input" (the HTML field generated) and read its "value" attribute - this is the selected patient's ID.
Another important note: I've displayed the value of the just set hidden field in an "alert", since viewing the browser source doesn't show the hidden field being updated.

$("tr").not(':first').click(
    function () {
      $(this).parent().find("tr").css("background", "");
      $(this).css("background","orange");
      $("#j_id_id13\\:testId").val($(this).find('td:last').find('input').attr("value"));
      alert($("#j_id_id13\\:testId").val());
    }
);

<h:inputHidden id="testId"/>

3. once I have selected the patient (a row in the table) and I click the button "Create appointment", I can retrieve the patient's ID from the hidden field with the following handler in the backing bean:
FacesContext context = FacesContext.getCurrentInstance();  
Map requestMap = context.getExternalContext().getRequestParameterMap();  
String value = (String)requestMap.get("j_id_id13:testId");
        
Patient patient;
if (value != null) {
    patient = getPatientById(value);
}
return "toCalendar";

Now I'm almost done. "Almost" again. Remember I'm on the page "appointments.jsp" and here I click "Patient search". What happens is that I'm basically sent back to the same page in the browser, but then the date line I've shown at the beginning will only display nulls:
<%
out.println("Data: " + request.getParameter("day") + "-" + request.getParameter("month") + "-" + request.getParameter("year"));
%>
That happens because when clicking the button, I'm actually creating a new request for this page, losing thus the URL parameters encoded from the calendar page. So, here's what I can do to preserve the date:
1. write the date into a cookie
2. use again hidden fields to pass the date around
3. do URL rewriting with an onclick JavaScript handler when clicking the "Patient search" button
4. use some form of browser storage (see HTML5, etc.)

And the ideas could just flow like crazy hadn't I found the simplest method:
<h:commandButton value ="Patient search:" action="#{PatientsSearch.getPatientsByName}">
    <f:param name="day" value="#{request.getParameter('day')}" />
    <f:param name="month" value="#{request.getParameter('month')}" />
    <f:param name="year" value="#{request.getParameter('year')}" />  
</h:commandButton>
Note here the EL expressions used to transmit the request parameters to the f:param and thus keep them in the request scope.
That's all folks ! Pffffffffff ....

P.S. I know you'll say there are many JSF plugins which can implement this easier, but sometimes I'm just too conservative ....