Using RichFaces 3 dataScroller and dataTable -components

RichFaces provides some nice AJAX-components for Java Server Faces but the documentation and examples could be better. RichFaces has great documentation compared to some other frameworks but it could be better with adding a little bit of real world and down to earth examples. So here is one example of using RichFaces dataScroller and dataTable -components with custom CSS-styling, backingBean and JSF-page snippets using Richfaces 3.3.2.SR1 and JSF 1.2_12.

Using RichFaces dataScroller and dataTable components has a big negative property: they work nicely if the amount of data is small but when the row count reaches to thousands they become sluggish or stop working. The rich:dataScroller needs the complete datamodel being loaded into memory and only displays a part of it. Not very efficient if the rowcount exceeds 1000 or so.

Anyways here is some real world example. The icons used in the examples for dataScroller are from Crystal Project Icons.

JSF-page

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<h:form id="myForm">
    <rich:dataScroller styleClass="dataScroller" id="scroller" 
        for="resultTable" maxPages="15" fastStep="3"
        renderIfSinglePage="false" immediate="false" 
        binding="#{backingBean.scroller}" page="#{backingBean.scrollerPage}">
        <f:facet name="first" >
            <h:graphicImage id="firstImage" styleClass="scroller" 
            url="images/crystal/tab_first.png" alt="first"/>
        </f:facet>
        <f:facet name="last">
            <h:graphicImage id="lastImage" styleClass="scroller" 
                url="images/crystal/tab_last.png" alt="last"/>
        </f:facet>
        <f:facet name="previous">
            <h:graphicImage id="prevImage" styleClass="scroller" 
                url="images/crystal/tab_left.png" alt="previous"/>
            </f:facet>
        <f:facet name="next">
            <h:graphicImage id="nextImage" styleClass="scroller" 
                url="images/crystal/tab_right.png" alt="previous"/>
        </f:facet>
        <f:facet name="fastforward">
            <h:graphicImage id="ffImage" styleClass="scroller" 
                url="images/crystal/tab_fastf.png" alt="next"/>
        </f:facet>
        <f:facet name="fastrewind">
            <h:graphicImage id="frImage" styleClass="scroller" 
                url="images/crystal/tab_fastr.png" alt="next"/>
        </f:facet>
        <f:facet name="first_disabled" >
            <h:graphicImage id="firstImage_d" styleClass="scroller" 
                url="images/crystal/tab_first.png" alt="first"/>
        </f:facet>
        <f:facet name="last_disabled">
            <h:graphicImage id="lastImage_d" styleClass="scroller" 
                url="images/crystal/tab_last.png" alt="last"/>
        </f:facet>
        <f:facet name="previous_disabled">
            <h:graphicImage id="prevImage_d" styleClass="scroller" 
                url="images/crystal/tab_left.png" alt="previous"/>
        </f:facet>
        <f:facet name="next_disabled">
            <h:graphicImage id="nextImage_d" styleClass="scroller" 
                url="images/crystal/tab_right.png" alt="next"/>
        </f:facet>
        <f:facet name="fastforward_disabled">
            <h:graphicImage id="ffImage_d" styleClass="scroller" 
                url="images/crystal/tab_fastf.png" alt="next"/>
        </f:facet>
        <f:facet name="fastrewind_disabled">
            <h:graphicImage id="frImage_d" styleClass="scroller" 
                url="images/crystal/tab_fastr.png" alt="previous"/>
        </f:facet>
        <f:facet name="controlsSeparator">
            <h:outputText id="sep" value=" " />
        </f:facet>
</rich:dataScroller>
 
<rich:dataTable styleClass="resultTable" id="resultTable" 
    rows="10" rowClasses=",odd"  columnClasses="col"  
    value="#{backingBean.resultList}" binding="#{backingBean.resultData}" 
    var="h" sortMode="multi">
    <rich:column sortBy="#{h.desc}">
        <f:facet name="header">
            <h:outputText value="description" />
        </f:facet>
        <h:commandLink value="#{h.desc}"
            action="#{backingBean.showRowData}">
            <f:param name="selectedRow" value="#{h.desc" />
        </h:commandLink>
    </rich:column>
    <rich:column sortBy="#{h.value}">
        <f:facet name="header">
            <h:outputText value="value" />
        </f:facet>
        <h:outputText value="#{h.value}" />
    </rich:column>
</rich:dataTable>
</h:form>

Backing Bean

Create some variables for dataScroller and getters and setters for them:

1
2
3
4
5
6
7
8
// RichFaces dataScroller variables
private HtmlDatascroller scroller = new HtmlDatascroller();
private String scrollerPage = "";
 
// Getting the clicked row's data
public String showRowdata() {
  MyDataModel current = (myDataModel) getResultData().getRowData();
}

CSS styling

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* =RichFaces DataScroller
----------------------------------------------- */
.rich-datascr {font-size: 1.1em;border: 0;}
.rich-table-cell {font-size: 1.0em;}
.rich-table-sortable-header {font-size: 1.1em;font-weight: bold;}
td.rich-datascr-button {background-color: #fff;border: 0px solid #ccc;text-decoration: none;}
td.rich-datascr-button-dsbld {background-color: #fff;}
.rich-datascr-ctrls-separator {padding-right: 5px;}
.rich-dtascroller-table {background: #fff;border: 0;}
.scroller {display: block;background-color: #fff;border: 1px solid #ccc;padding: 3px 3px;margin: 0px 5px 5px 5px;text-decoration: none;}
.scroller:hover {background-color: #eee;}
td.rich-datascr-button-dsbld .scroller {background-color: #eee;}
td.rich-datascr-inact {font-size: 1.2em;color: #000;border: 0;}
td.rich-datascr-inact:hover {text-decoration: underline;}
td.rich-datascr-act {font-size: 1.2em;text-decoration: underline;}
td.rich-datascr-act {border: 0;font-weight: bold;}

Selecting All rows with JavaScript

Add to the JSF-page a new column which has the checkbox. We are using JavaScript to loop through the input fields which are after :tu -ending id-field.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<rich:column>
  <f:facet name="header">
    <h:panelGroup layout="block">
      <script type="text/javascript">
        //<![CDATA[
        // RichFaces datatable select all -checkbox
        function checkAllCheckboxesInTable( inputId, state ){
          var commonIdPart = inputId.substr(0, inputId.lastIndexOf(':'));
          var tableId = commonIdPart + ':tu'
          var tableElement = document.getElementById( tableId );
          var inputs = tableElement.getElementsByTagName('input');
          for (var i = 0; i <= inputs.length; i++){
            var input = inputs[i];
            if (input != undefined) {
              if( input.getAttribute('type') == 'checkbox' && state){
                input.setAttribute('checked', state);
              } else{
                input.setAttribute('checked', false);
                input.removeAttribute('checked');
              }
            }
          }
        }
        //]]>
      </script>
      <h:selectBooleanCheckbox id="t0" onclick="checkAllCheckboxesInTable( this.id, this.checked );">
        <a4j:support event="onchange" reRender="resultTable"/>
      </h:selectBooleanCheckbox>
    </h:panelGroup>
  </f:facet>
  <h:selectBooleanCheckbox id="t1" value="#{h.selected}" />
</rich:column>

Selecting All rows in backing bean

You can also check all the checkboxes from the backingBean but it has problems with table ordering and when the lists order changes the selection goes wrong.

Add to the JSF-page a new column:

1
2
3
4
5
6
7
8
<rich:column>
  <f:facet name="header">
    <h:selectBooleanCheckbox id="t0" value="#{backingBean.selectedAll}" onclick="this.blur()">
        <a4j:support event="onchange" actionListener="#{backingBean.selectAll}" reRender="resultTable, t0, t1"/>
      </h:selectBooleanCheckbox>
    </f:facet>
  <h:selectBooleanCheckbox id="t1" value="#{h.selected}" />
</rich:column>

Make a new method to your backingBean:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void selectAll(ActionEvent event) {
	logger.info("*** backingBean.selectAll(): " + scrollerPage + " ***");
 
	// get the current scroller page
	int page = Integer.valueOf(scrollerPage).intValue();
	if (page != 0) {
	    page = page - 1;
	}
	int start = page * 10;
	int stop = (page * 10) + 10;
	if (stop > getResultList().size()) {
	    stop = getResultList().size();
	}
	logger.debug("> page: " + page + "; start: " + start + "; stop: " + stop);
 
	// check the boxes on the active page
	for (int i = start; i < stop; i++) {
	    logger.debug("> valitaan: " + i + "; " + selectedAll);
	    getResultList().get(i).setSelected(selectedAll);
	}
}

Eclipse: Class file name must end with .class exception in search

Eclipse is nice IDE but it has it’s own problems. This time the Java Search and Open Type -search produced an error saying “Class file name must end with .class”. Very helpfull. Fortunately almost all the answers in the world can be found in the Internet and so with a quick googling the solution to this annoying problem was found on Stack Overflow.

I had already tried Project -> Clean… and closing Eclipse, deleting all the built class files and restarting Eclipse to no avail as was the original question author. The right answer lies in deleting the corrupted search index which is explained in Eclipse bug’s #269820 comment.

How to delete the search index:

  1. Close Eclipse
  2. Delete workspace/.metadata/.plugins/org.eclipse.jdt.core/*.index
  3. Delete workspace/.metadata/.plugins/org.eclipse.jdt.core/savedIndexNames.txt
  4. Start Eclipse again

This fixed the issue for me.

Redirect HTTP and HTTPS traffic to Tomcat’s ports

Apache Tomcat likes with default settings to listen to requests on 8080 and 8443 ports but it is more enjoyable to use the more common 80 and 443 ports for HTTP and HTTPS traffic. This way the user don’t have to put those pesky port numbers after the address. Of course you could just tell Tomcat to listen to those ports but it has some negative sides: hassle with the startup and running Tomcat as root.

Luckily it is easy to tell the system to redirect the traffic from some port to other. Just define some new xinetd services in /etc/xinetd.d/tomcat.

# vim /etc/xinetd.d/tomcat
 
# Redirects any requests on port 80 to port 8080 (where Tomcat is listening)
service tomcat-http
{
        disable                 = no
        flags                   = REUSE
        wait                    = no
        user                    = root
        socket_type         = stream
        protocol                = tcp
        port                    = 80
        redirect                = localhost 8080
        log_on_success  -= PID HOST DURATION EXIT
 
        #per_source = UNLIMITED
        #instances = UNLIMITED
}
 
# Redirects any requests on port 443 to port 8443 (where Tomcat is listening)
service tomcat-https
{
        disable                 = no
        flags                   = REUSE
        wait                    = no
        user                    = root
        socket_type         = stream
        protocol                = tcp
        port                    = 443
        redirect                = localhost 8443
        log_on_success  -= PID HOST DURATION EXIT
 
        #per_source = UNLIMITED
        #instances = UNLIMITED
}

(via Securing Linux for Java services: The port dilemma)

Xinetd puts a connection limit per source IP, by default and this causes the service to become unresponsive when there are dozens of queries a second. You see the following kind of line in your messages log file: “xinetd[2049]: FAIL: tomcat-https per_source_limit from=123.456.789.123”. To correct this, uncomment the per_source and instances lines in your xinet.d file and restart it.

Also add those xinetd services to /etc/services.

# vim /etc/services
http        80/tcp     www www-http tomcat-http # WorldWideWeb http
http        80/udp     www www-http tomcat-http # WorldWideWeb HTTP
http        443/tcp    tomcat-https # WorldWideWeb HTTPS
http        443/udp    tomcat-https # WorldWideWeb HTTPS

And now just restart the xinetd and admire how your traffic is redirected to Tomcat’s ports.

# service xinetd restart

Force everything to transmit through HTTPS
If you also want to redirect all HTTP traffic to HTTPS you can add the following section to you Tomcat web.xml:

<web-resource-collection>
    <web-resource-name>Protected Context</web-resource-name>
    <url-pattern>/*</url-pattern>
</web-resource-collection>
<!-- auth-constraint goes here if you requre authentication -->
<user-data-constraint>
    <transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>

If you are using this redirection of all traffic to HTTPS with JIRA and want to attachments working also with Internet Explorer then you must add the following to your jira.xml (f. ex. /opt/tomcat/conf/Catalina/localhost/jira.xml). This is a Internet Explorer bug, for more information see http://jira.atlassian.com/browse/JRA-8179.

<Context ...>
...
<!-- for IE bug, see http://jira.atlassian.com/browse/JRA-8179-->
<Valve className="org.apache.catalina.authenticator.NonLoginAuthenticator"
disableProxyCaching="false" />
...
</Context>