donderdag 24 februari 2011

How to never display a horizontal scrollbar in a table

Technology: ADF11g
Developed in: JDeveloper 11.1.1.3.0
Browsers tested: Firefox 3.6.13 and Internet explorer 7 (7.0.6002.18005)
Used database schema: HR
Used tables: EMPLOYEES


Summary



Display an table layout in ADF11 is easy, but I’m annoyed by the horizontal scrollbar in the button of the table. A vertical scrollbar I can imagine, I believe it’s more user friendly then the page range that ADF10 had.

Let’s first see what ADF11 creates when dragging and dropping the Employees table from the data control palette. When the browser has a ‘reasonable’ size it looks like this:



Why is the table that small? The screen is so big I would prefer the table being wider so the vertical scrollbar can disappear. When we make the screen (very) small it’s even worse:




Now we have to scrollbars. The scrollbar underneath the Cancel button is just to make the vertical scrollbar of the table visible. But actually this scrollbar (the one underneath the Cancel button) is the one I would like to keep it’s the scrollbar of the complete page content (like word has a scrollbar if you make the screen smaller then a page).

This issues can easily be solved. Just put in the table:

styleClass="AFStretchWidth"

This is equal to width 100%. Then if we have a wide screen it looks like this:



But know I wonder, why such a use gap between the last column and the vertical scrollbar? If we make the screen small it looks like this:



Now we only have one scrollbar, but if we would add items underneath the table wider as the screen a second horizontal scrollbar will be displayed at the bottom of the page (content so above the Marianne Horsch © ADF footer).

So still I’m not really satisfied by how the table looks. Now look at this wide screen:



That looks fine! Scrollbar just after the last column just where I would expect it. But in a small screen??



Also just like I would like it, at most one horizontal scrollbar but not in the table but at the bottom of the page content.

But how did I achieve this? Well I set the width instead of the styleClass. To be precise, I set the width to 1276.

Calculate the width of the table



You may think 1276, how did she come up with that number? Trial and error? And does it really has to be exactly 1276?

First answer the second question:



This picture is width 1280, there is now a gap between the last column and the scrollbar. So yes it needs to be exactly 1276 (for this table).

Then the first question, was it trial and error? No it wasn’t, you can calculate the width of the table. And it is as follows:
  • Sum all the widths of the columns
  • Add to this the number of columns multiplied by 5
  • If the table has a vertical scrollbar then add 16
In this example this is:
  • Sum of all width of the columns is 1200 (12 columns each with width 100)
  • 12 columns multiplied by 5 is 60
  • We have a scrollbar so add 16
  • Makes total 1200 + 60 + 16 = 1276.

A generic solution



Of course, you don’t want to recalculate the width of the table every time you add or remove a column, or what if columns are rendered based on criteria such as user roles? And display a scrollbar or not is not fixed as well.

Before the solution is described let us first take a look at the example application:

Setup example application



For this blog an example application is created based on the HR schema. The example application contains an employees table with a filter. For this employees table a bounded task flow is created which is started from the menu.

Model layer



Create the following entities:

Entity name Based on table of HR schema Customizations made
Employee EMPLOYEES None


Create the following view objects:




View object name Based on entities Customizations made
EmployeesView Employee Added 2 bind variables:
  • b_min_salary (Number)
  • b_min_salary (Number)
Changed where clause:

(:b_min_salary IS NULL OR (:b_min_salary IS NOT NULL AND Employee.SALARY >= :b_min_salary)) AND (:b_max_salary IS NULL OR (:b_max_salary IS NOT NULL AND Employee.SALARY <= :b_max_salary))


Create an application module HrAppModule which exposes the EmployeesView.



Unbounded task flow



The unbounded task flow from where we start with the solution looks like this:



There are no customizations made, the task flow is created by drag and drop.

Bounded task flow



The employees task flow is a bounded task flows.



The next properties are set (for both bounded task flows):

Property Value
usePageFragments false

Share data controls with calling task flow true


Table page



The table pages are created by drag and drop from the Data Controls. The table is dropped as ADF Read-only table with Row Selection and Sorting checked, all columns are displayed.





Underneath the table a Cancel button (af:commandButton) is added which ends the task flow.

af:commandButton property Value
text Cancel
id cancel
action cancel
immediate true


Above the table a panelFormLayout is added that contains 2 input text items and a button.

The input text items have the following properties:

Property First input text Second input text
binding #{employeeBean.minSalaryFilter} #{employeeBean.maxSalaryFilter}
label Minimum salary Maximum salary
id minSalaryFilter maxSalaryFilter
f:validator binding #{bindings.Salary.validator} #{bindings.Salary.validator}
af:convertNumber groupingUsed false false
af:convertNumber pattern #{bindings.Salary.format} #{bindings.Salary.format}


The af:commandButton has the following properties:

Property Value
text Filter
id filter
actionListener #{employeeBean.filter}


Add to the page definition of the table page inside the bindings tag:

<attributeValues IterBinding="EmployeesViewIterator"

id="Salary">

<AttrNames>

<Item Value="Salary"/>

</AttrNames>

</attributeValues>

Set the partialTrigger property of the table:

partialTriggers="::filter"


Employee bean



In the table page references are made to the employeeBean. This bean class is defined in the employee task flow:

Managed bean property Value
Name employeeBean
Class nl.hr.demo.view.beans.EmployeeBean
Scope Request


The bean class contains two class variables RichInputText minSalaryFilter and RichInputText maxSalaryFilter and their accessors.

The bean class also contains the actionListener implementation. In this implementation the EmployeesView is retrieved from the application module and the values of minSalaryFilter and maxSalaryFilter are copied to the EmployeesView and the view is queried:

public void filter (ActionEvent actionEvent) {

EmployeesViewImpl view = getService().getEmployeesView();

view.setb_min_salary(getValue(minSalaryFilter));

view.setb_max_salary(getValue(maxSalaryFilter));

view.executeQuery();

}



private Number getMValue(RichInputText item) {

if (item == null || item.getValue() == null) {

return null;

}

return new Number(((BigDecimal) item.getValue()).intValue());

}



private static HrAppModuleImpl getService() {

DCBindingContainer bc = (DCBindingContainer)FacesContext.getCurrentInstance().getApplication().evaluateExpressionGet(FacesContext.getCurrentInstance(), "#{bindings}", BindingContainer.class);

DCDataControl dc = bc.findDataControl("HrAppModuleDataControl");

return (HrAppModuleImpl) dc.getDataProvider();

}

The class uses the next imported classes:

Class name Package
BigDecimal java.math
FacesContext javax.faces.context
ActionEvent javax.faces.event
DCBindingContainer oracle.adf.model.binding
DCDataControl oracle.adf.model.binding
RichInputText oracle.adf.view.rich.component.rich.input
BindingContainer oracle.binding
Number oracle.jbo.domain

Fix the table width



Now we’re ready to fix the table width with a generic solution. For this solution we need:
  • A bean class that binds the table and calculates the width (in or exclusive scrollbar)
  • Set properties in the table to use the bean class

Bean class



Define in the unbounded task flow a new bean:

Managed bean property Value
Name tableBean
Class nl.hr.demo.view.util.TableBean
Scope Request


The bean class contains a class variables RichTable table and its accessors.

The bean class contains 1 method which returns the width of the table as an int. The width is calculated by:
  • Loop over all columns of the table:
    • If the column is rendered and visible then add its width to a local integer and add 1 to another local integer that counts the number of columns.
  • Add to the width the number of columns multiplied by 5.
  • Retrieve the number of rows in the view object that is displayed by the table:
    • Get the value property of the table cast it to a CollectionModel and get the estimatedRowCount value.
  • If the number of rows is bigger than the number of rows displayed in the table (the autoHeightRows property of the table) then add 16 to the width.
  • Return the width.

public int getWidth() {

if (table == null) {

return 600;

}

try {

int width = 0;

int columns = 0;

List list = table.getChildren();

for (int i = 0; i < list.size(); i++) {

UIComponent component = list.get(i);

if (component instanceof RichColumn) {

RichColumn column = (RichColumn) component;

if (column.isRendered() && column.isVisible()) {

width += new Integer(column.getWidth()).intValue();

columns++;

}

}

}

width += (columns * 5);

int nrRows = 0;

if (table.getValue() != null) {

CollectionModel tableData = (CollectionModel) table.getValue();

nrRows = tableData.getEstimatedRowCount();

}

if (nrRows > table.getAutoHeightRows()) {

width += 16;

}

return width;

} catch (Exception e) {

e.printStackTrace();

return 600;

}

}

The class uses the next imported classes:

Class name Package
List java.util
UIComponent javax.faces.component
RichColumn oracle.adf.view.rich.component.rich.data
RichTable oracle.adf.view.rich.component.rich.data
CollectionModel org.apache.myfaces.trinidad.model

Table properties



Set the following properties of the employees table:

Property Value Description
binding #{tableBean.table} Binds the table to the RichTable class variable of the TableBean
width #{tableBean.width} The width is set to the result of the getWidth method of the TableBean
autoHeightRows 10 The (maximum) number of rows displayed in the screen
contentDelivery immediate Must be set to immediate otherwise the autoHeightRows property doesn’t work


Note: Firefox displays one row more than set in the autoHeightRows property . When the autoHeightRows property is set to 10 and there 11 rows in the view object the table bean adds space for a scrollbar but because firefox displays 1 row more the scrollbar space is empty.

Too avoid this a small correction should be made to the TableBean (only tested in IE and Firefox):

Change:

if (nrRows > table.getAutoHeightRows()) {

To:

if (nrRows > (table.getAutoHeightRows() + browserCorrectionRowsDisplayed())) {

The implementation of the browserCorrectionRowsDisplayed method:

public int browserCorrectionRowsDisplayed() {

String browser = (RequestContext.getCurrentInstance()).getAgent().getAgentName();

if (browser != null && browser.equalsIgnoreCase("gecko")) {

return 1;

}

return 0;

}

The result:

Geen opmerkingen:

Een reactie plaatsen