Quick-Start Guide

OptoGantt — Jakarta Faces / JSF

Follow these steps to add an interactive Gantt chart to your Jakarta Faces application. By the end you will have the chart shown below running in your own project, ready to connect to your own data.

Live example — what you will build

Filter
Tue, 21 AprWed, 22 AprThu, 23 AprFri, 24 AprSat, 25 AprSun, 26 AprMon, 27 AprTue, 28 AprWed, 29 AprThu, 30 AprFri, 1 MaySat, 2 MaySun, 3 May
Alice
  • Project Kickoff Project Kickoff | Project Kickoff | Alice | Completed
Bob
  • Development Sprint Development Sprint | Development Sprint | Bob | In progress
Carol

This chart is fully interactive — use the filter pane to adjust the date range and period. The stripe on the bar crossing today's date is the built-in "now" indicator.

Part 1 — Display-only Gantt chart

Start here. Steps 1–6 give you a working Gantt chart with no user interaction. If you only need to display data, you can stop after Step 6.

1
Install the JAR files

Download optogantt-1.0.0.zip from Gumroad and unzip it. Install both JARs to your local Maven repository by running the following command once for each, substituting the filename:

Shell
mvn install:install-file \
  -Dfile=gantt-core-1.0.0.jar \
  -DgroupId=com.optomus.gantt \
  -DartifactId=gantt-core \
  -Dversion=1.0.0 \
  -Dpackaging=jar

Repeat for gantt-jsf-1.0.0.jar (artifactId: gantt-jsf). The remaining JARs are for other frameworks and are not needed for this guide.

2
Add the Maven dependency

Add the following dependency to your pom.xml:

pom.xml
<dependency>
    <groupId>com.optomus.gantt</groupId>
    <artifactId>gantt-jsf</artifactId>
    <version>1.0.0</version>
</dependency>

gantt-core is a transitive dependency of gantt-jsf and is resolved automatically. You do not need to declare it separately.

3
Add CSS to your Facelets template

The gantt-jsf JAR packages GanttChart.css and GanttFilterPane.css as JSF library resources under the gantt library name. Add them to your page's <h:head> using h:outputStylesheet:

yourpage.xhtml
<h:head>
    <h:outputStylesheet library="gantt" name="GanttChart.css"/>
    <h:outputStylesheet library="gantt" name="GanttFilterPane.css"/>
</h:head>
No manual copying needed: JSF serves these CSS files automatically from the META-INF/resources/gantt/ directory inside the JAR. The library="gantt" attribute maps to that directory name.
4
Build your Gantt bars

Create your bar data using GanttBar. Each bar requires a task name, row name, start datetime, end datetime, text colour, bar background colour, hover text, and an optional hyperlink (null if not needed).

YourBackingBean.java
import com.optomus.gantt.core.model.GanttBar;
import com.optomus.gantt.core.model.GanttColour;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;

// Build bars relative to today
LocalDate today = LocalDate.now();

List<GanttBar> bars = List.of(
    new GanttBar(
        "Project Kickoff",              // task name
        "Alice",                        // row name
        LocalDateTime.of(today.minusDays(5), LocalTime.of(9,  0)),
        LocalDateTime.of(today.minusDays(1), LocalTime.of(17, 0)),
        GanttColour.WHITE,              // text colour
        new GanttColour("#2a4a7a"),     // bar colour
        "Project Kickoff | Alice | Completed",  // hover text
        null                            // hyperlink
    ),
    new GanttBar(
        "Development Sprint",
        "Bob",
        LocalDateTime.of(today.minusDays(3), LocalTime.of(9,  0)),
        LocalDateTime.of(today.plusDays(4),  LocalTime.of(17, 0)),
        GanttColour.WHITE,
        new GanttColour("#1a6b5a"),
        "Development Sprint | Bob | In progress",
        null
    ),
    new GanttBar(
        "Testing & QA",
        "Carol",
        LocalDateTime.of(today.plusDays(2),  LocalTime.of(9,  0)),
        LocalDateTime.of(today.plusDays(6),  LocalTime.of(17, 0)),
        GanttColour.WHITE,
        new GanttColour("#5b3a6e"),
        "Testing & QA | Carol | Upcoming",
        null
    )
);
Row order: rows appear in the chart in the order they are first encountered in the bar list, unless you specify row names explicitly in Step 5.
5
Configure and build the chart in your backing bean

Inject the request-scoped GanttChart CDI bean, supply your data, and call build() in your @PostConstruct method. GanttChart is request-scoped — a fresh instance is created for each HTTP request automatically.

YourBackingBean.java
import com.optomus.gantt.jsf.components.GanttChart;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;

@Named
@RequestScoped
public class YourBackingBean {

    @Inject
    private GanttChart ganttChart;

    @PostConstruct
    public void init() {
        LocalDateTime chartStart = LocalDateTime.of(
            LocalDate.now().minusDays(6), LocalTime.MIDNIGHT);

        ganttChart.setGanttBars(buildBars());  // your bar list from Step 4
        ganttChart.setRowNames(List.of("Alice", "Bob", "Carol"));
        ganttChart.setStartDate(chartStart);
        ganttChart.setColumnDuration(Duration.ofDays(1)); // one column per day
        ganttChart.setNumColumns(14);                     // 14-day window
        ganttChart.build();
    }
}
Important: always call ganttChart.build() after setting all parameters. Forgetting this call will result in a NullPointerException at render time.
6
Include the chart template in your Facelets page

The gantt-jsf JAR contains the Facelets template. Include it in your page using ui:include. The template references the ganttChart CDI bean by name via EL expressions — no additional model wiring is needed.

yourpage.xhtml
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="jakarta.faces.html"
      xmlns:ui="jakarta.faces.facelets">
<h:head>
    <h:outputStylesheet library="gantt" name="GanttChart.css"/>
    <h:outputStylesheet library="gantt" name="GanttFilterPane.css"/>
</h:head>
<h:body>
    <div id="ganttChartZone">
        <ui:include src="/WEB-INF/gantt/GanttChart.xhtml"/>
    </div>
</h:body>
</html>
If you only need a display-only chart, stop here. Steps 1–6 are all you need. Continue below to add the interactive filter pane with AJAX support.

Part 2 — Interactive filter pane with AJAX

Add the filter pane so visitors can adjust the chart's date range and period. The filter pane uses JSF's built-in f:ajax for partial page updates — only the chart and filter zones are re-rendered, avoiding a full page reload.

7
Inject GanttFilterPane and implement GanttFilterListener

GanttFilterPane is session-scoped — one instance persists across requests for each visitor. Inject it into your backing bean and register a GanttFilterListener from your @PostConstruct method. The listener receives resolved filter parameters after the pane validates and processes a submission.

YourBackingBean.java
import com.optomus.gantt.core.GanttFilterListener;
import com.optomus.gantt.core.model.GanttColumnDuration;
import com.optomus.gantt.jsf.components.GanttChart;
import com.optomus.gantt.jsf.components.GanttFilterPane;
import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import java.time.*;
import java.util.List;

@Named
@RequestScoped
public class YourBackingBean {

    @Inject private GanttChart      ganttChart;
    @Inject private GanttFilterPane ganttFilterPane;

    // Store resolved filter state — use a session-scoped service
    // bean in production rather than fields on a request-scoped bean
    private LocalDateTime chartStart     = defaultStart();
    private Duration      columnDuration = Duration.ofDays(1);
    private int           numColumns     = 14;

    @PostConstruct
    public void init() {
        ganttFilterPane.setFilterListener(new GanttFilterListener() {
            @Override
            public void onFilter(LocalDateTime start,
                                 Duration duration, Integer cols) {
                chartStart     = start;
                columnDuration = duration;
                numColumns     = cols;
                buildChart();
            }
            @Override
            public void onClearFilter() {
                chartStart     = defaultStart();
                columnDuration = Duration.ofDays(1);
                numColumns     = 14;
                buildChart();
            }
        });
        buildChart();
    }

    private void buildChart() {
        ganttChart.setGanttBars(buildBars());
        ganttChart.setRowNames(List.of("Alice", "Bob", "Carol"));
        ganttChart.setStartDate(chartStart);
        ganttChart.setColumnDuration(columnDuration);
        ganttChart.setNumColumns(numColumns);
        ganttChart.build();
    }

    private static LocalDateTime defaultStart() {
        return LocalDateTime.of(
            LocalDate.now().minusDays(6), LocalTime.MIDNIGHT);
    }
}
Session state: chartStart, columnDuration and numColumns must survive across requests. Use a dedicated @SessionScoped CDI bean to hold filter state in production rather than fields on a request-scoped backing bean.
8
Add the filter pane template and AJAX to your Facelets page

Include the filter pane template above the chart. The template's Apply Filter button and Clear filter link already use f:ajax internally — they re-render only ganttFilterZone and ganttChartZone on each interaction, with full-page fallback if JavaScript is disabled.

yourpage.xhtml
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="jakarta.faces.html"
      xmlns:ui="jakarta.faces.facelets">
<h:head>
    <h:outputStylesheet library="gantt" name="GanttChart.css"/>
    <h:outputStylesheet library="gantt" name="GanttFilterPane.css"/>
</h:head>
<h:body>
    <div id="ganttFilterZone">
        <ui:include src="/WEB-INF/gantt/GanttFilterPane.xhtml"/>
    </div>

    <div id="ganttChartZone">
        <ui:include src="/WEB-INF/gantt/GanttChart.xhtml"/>
    </div>
</h:body>
</html>
No JavaScript to write: the f:ajax partial rendering is declared in the filter pane template itself. The ganttFilterZone and ganttChartZone div ids must match exactly — the template targets them by these ids when re-rendering after each filter interaction.
9
Add Flatpickr for desktop datetime input

The filter pane renders an input[type="datetime-local"] field. On desktop browsers, native datetime pickers vary significantly across browsers. Flatpickr provides a consistent, polished picker on all desktop browsers while mobile browsers retain their superior native pickers.

Add the following script to your page, after the closing </h:form> tag or at the bottom of <h:body>. The handleAjaxEvent function reinitialises Flatpickr after each AJAX partial update so the picker remains active after Apply Filter or Clear filter:

yourpage.xhtml — Flatpickr script
<h:outputScript>
(function () {
    if (/Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i
            .test(navigator.userAgent)) return;

    var css = document.createElement('link');
    css.rel  = 'stylesheet';
    css.href = 'https://cdnjs.cloudflare.com/ajax/libs/flatpickr/4.6.13/flatpickr.min.css';
    document.head.appendChild(css);

    var style = document.createElement('style');
    style.textContent =
        'input.flatpickr-input[readonly] {' +
        'width:100%;padding:8px 10px;min-height:2.4em;' +
        'box-sizing:border-box;border:1px solid #e0d5c8;' +
        'border-radius:6px;font-family:Source Sans 3,sans-serif;' +
        'font-size:0.875rem;color:#2d1f14;background:#ffffff;' +
        'cursor:pointer;transition:border-color 0.2s,box-shadow 0.2s;}' +
        'input.flatpickr-input[readonly]:focus {' +
        'outline:none;border-color:#4a9b8e;' +
        'box-shadow:0 0 0 3px rgba(74,155,142,0.15);}';
    document.head.appendChild(style);

    var config = {
        enableTime:      true,
        dateFormat:      'Y-m-dTH:i',
        altInput:        true,
        altFormat:       'd M Y h:i K',
        time_24hr:       false,
        minuteIncrement: 60,
        allowInput:      true
    };

    function initFlatpickr() {
        var fields = document.querySelectorAll(
            'input[type="datetime-local"]');
        fields.forEach(function (el) { flatpickr(el, config); });
    }

    // Reinitialise Flatpickr after each JSF AJAX partial update
    window.handleAjaxEvent = function(data) {
        if (data.status === 'success') { initFlatpickr(); }
    };

    var script = document.createElement('script');
    script.src =
        'https://cdnjs.cloudflare.com/ajax/libs/flatpickr/4.6.13/flatpickr.min.js';
    script.onload = initFlatpickr;
    document.head.appendChild(script);
}());
</h:outputScript>
JSF AJAX lifecycle: handleAjaxEvent is the onevent callback referenced in the filter pane template's f:ajax declarations. JSF calls it automatically after each partial page update — you do not need to wire it up manually.
Your chart is now fully interactive with AJAX filter support. Continue below for optional customisation.

Part 3 — Optional customisation

These steps are independent of each other — apply any combination that suits your use case.

10
Customise chart colours Optional

Override the default heading, border, and alternate row colours by calling the relevant setters before build(). All colours accept CSS hex strings via GanttColour.

YourBackingBean.java
ganttChart.setHeadingTextColour(new GanttColour("#ffffff"));
ganttChart.setHeadingBackgroundColour(new GanttColour("#3d2b1f"));
ganttChart.setBorderColour(new GanttColour("#6b4c35"));
ganttChart.setAlternateRowsBackgroundColour(new GanttColour("#faf7f3"));
ganttChart.build();

If only setHeadingBackgroundColour() is called, border and alternate row colours are derived automatically as darker and lighter shades respectively.

11
Customise row height and name column width Optional
YourBackingBean.java
ganttChart.setNameColumnWidth("180px");  // any valid CSS width
ganttChart.setRowHeightInPixels(40);
ganttChart.build();
12
Internationalise labels via a properties file Optional

OptoGantt loads all filter pane labels from GanttMessages.properties inside the gantt-core JAR using JSF's f:loadBundle. To override any label, place your own GanttMessages.properties earlier on the classpath — for example in src/main/resources/com/optomus/gantt/core/.

GanttMessages.properties — overridable keys
# Filter pane labels
label.startDate=Start
label.columnDuration=Period
button.apply=Apply Filter
link.clear=Clear filter

# Period dropdown options
duration.FOURTEEN_DAYS=14 Days
duration.ONE_MONTH=1 Month
duration.THREE_MONTHS=3 Months
duration.SIX_MONTHS=6 Months
duration.ONE_YEAR=1 Year
No Spring MessageSource needed: label resolution uses Java's standard ResourceBundle via JSF's f:loadBundle — no framework-specific configuration is required. Locale-specific files such as GanttMessages_fr.properties are picked up automatically based on the active JSF locale.
13
Add hyperlinks to bars Optional

Pass a URL as the final constructor argument to GanttBar. Clicking the bar will navigate to that URL. Pass null if no link is needed.

YourBackingBean.java
new GanttBar(
    "Sprint Review",
    "Alice",
    start, end,
    GanttColour.WHITE,
    new GanttColour("#2a4a7a"),
    "Sprint Review | Alice | Click for details",
    "https://your-project-tracker.example.com/sprint/42"
)

Troubleshooting

Common issues and their solutions.

!
NullPointerException at render time

Almost always caused by forgetting to call ganttChart.build() after setting parameters. Ensure build() is the last call in your @PostConstruct method before the page renders.

!
Filter pane Apply button causes a full page reload

Confirm that the ganttFilterZone and ganttChartZone div ids in your Facelets page match exactly what the filter pane template targets. If the ids are missing or misnamed, JSF's f:ajax cannot find the elements to re-render and falls back to a full page reload.

!
CSS not loading

Confirm that gantt-jsf-1.0.0.jar is on the classpath and that your template includes both h:outputStylesheet library="gantt" name="GanttChart.css" and h:outputStylesheet library="gantt" name="GanttFilterPane.css" inside <h:head>. JSF serves these automatically from the JAR's META-INF/resources/gantt/ directory.

!
Filter pane does not reflect chart state after navigation

Ensure chart state (chartStart, columnDuration, numColumns) is stored in a @SessionScoped CDI bean rather than in a request-scoped backing bean. Request-scoped beans are destroyed and recreated on each request — any state held in them is lost between requests.

!
Flatpickr does not reinitialise after AJAX update

Ensure the handleAjaxEvent function is defined in your page before the Flatpickr script loads (Step 9). JSF calls handleAjaxEvent after each partial page update — if the function is not defined at that point, Flatpickr will not be reinitialised and the enhanced date picker will disappear after the first filter interaction.

Next steps

Try the live demo

Visit gantt.nz to explore the full interactive demo — add your own bars, adjust the filter pane, and see all features in action.

Get support

Questions or unexpected behaviour? Email support@gantt.nz. We aim to respond within 24 hours on New Zealand business days.