1. Introduction
JWt is a Java library for developing web applications. The library uses the standard Java Servlet infrastructure, and therefore, a JWt web application may be deployed in existing servlet containers or JEE web application servers.
The API is widget-centric, and inspired by existing Graphical User Interface (GUI) APIs: a widget is a self-contained class which encapsulates all event handling related to a user interface element. The library offers an abstraction of many web-specific implementation details, including event handling and graphics support. In this way, the library chooses a rendering strategy based on the browser’s capabilities (AJAX or plain HTML). Still, the library supports the web semantics of URLs, bookmarks, and forward/backward navigation and therefore also search engine interoperability.
2. Library overview
2.1. Widgets
The WWidget
class represents a widget,
which provides an abstraction of a visual entity. The entire
user interface is specified by creating a hierarchical structure of
widgets, rooted at
WApplication.getRoot()
. By
reacting to events related to these widgets, you can perform business
logic, and manipulate the widget hierarchy to update the user
interface.
Any descendent class of WWidget
is a
self-contained (reusable) class that encapsulates both the look and
behavior, enabling the design of the user interface in an orthogonal
way.
2.1.1. Layout
Widgets are layed out (with a few exceptions) following their hierarchical structure. You have two main options for the layout of children within a container. Either you use a CSS based layout, in which case the CSS style properties of the container and children together determine the result: each child manages its layout with respect to its sibling following a (rather complex) set of rules. Alternatively, JWt provides layout managers that may be used for layout.
CSS layout considers two important categories of layout. Text-like widgets (inline) flow with sibling inline widgets in lines, wrapping at the right edge of the parent container. In contrast, widgets displayed as a block stack vertically with respect to sibling widgets. Block widgets allow more control over their position and size than inline widgets, and may also float to the left or right border of the parent container.
Layout managers are implemented by classes that derive from
WLayout
and are used in conjunction with
the WContainerWidget
class.
2.1.2. Style
For visual markup of widgets, the recommended way is to use CSS style
sheets. These allow the visual look to be defined separately from the
the rest of the application. External stylesheets may be loaded using
WApplication.useStyleSheet()
and the external stylesheet may be manipulated using
WApplication.getStyleSheet()
.
In the stylesheets, you describe rules that are prefixed by CSS
selectors. By setting matching style classes for your widgets using
WWidget.setStyleClass()
,
these rules will be applied to your widgets. The recommended way for
the visual response to events is by changing the style class for the
widget.
In addition to style sheets, Wt also supports the direct manipulation
of a widget’s style, using
WWidget.getDecorationStyle()
.
2.1.3. Widget containers
With a few exceptions, all widgets are a child of (and contained in) a
container widget such as
WContainerWidget
or
WTableCell
. A widget is inserted into a
WContainerWidget
by adding the widget to the container using
WContainerWidget.addWidget()
,
or by passing the parent container as an argument to its
constructor. You may also add a widget to a container using a
layout manager.
2.2. Application URL(s)
A JWt application, like any other servlet, is deployed at a specific
location (URL) within your servlet container.
This location is configured through the url-pattern
defined
in the servlet-mapping
within your web.xml
deployment descriptor,
and the context at which the application is deployed. In this mode, a
JWt application is a single page web application: the URL does not
change.
A JWt application may also manage internal paths, which are URLs
inside your application URL. To allow the servlet to handle all
internal paths too, you need to set an url-pattern
that ends with
"/*"
. Then, the internal path may be set and read using
WApplication.setInternalPath()
and
WApplication.getInternalPath()
. When
the internal path changes, this is reflected in the browser URL and an
entry is added to the browser history, allowing the user to use the
back and forward buttons to navigate through your application.
When AJAX is available, the library will always avoid rerendering the
entire widget tree. For a plain HTML session, the session ID is appended
to the URL to avoid the session from reloading when the user navigates
using a WAnchor
to a new internal URL.
To effectively change the internal path and obtain consistent
behavior with or without JavaScript, you should use a
WAnchor
to
let the user navigate to a new internal path.
The easiest way to do
this is by supplying a
WLink
with
LinkType.InternalPath
.
This refers the anchor to a URL generated by
WApplication.getBookmarkUrl()
for the new internal path (handling the plain HTML case), and binds a
JavaScript listener to its
clicked()
signal, which changes
the internal path (handling the AJAX case).
Finally, you can listen for path changes using the
WApplication.internalPathChanged()
event to react to the user navigating through his history.
When your applications uses internal URLs, this has consequences for relative URLs to external resources (style sheets, images, JavaScript files, etc.), since these are resolved taking into account the current relative URL. All relative URLs that are known to the application are automatically replaced by JWt with an absolute URL that resolves these directly within the deployment location. You should use absolute URLs in CSS or XHTML for them to work within each internal path, since these cannot be fixed by JWt.
2.3. Startup, session management and request handling
JWt provides the abstract WtServlet
class
which implements the controller for an application. Every JWt
application must extend this class and implement its
createApplication()
to return a new WApplication
instance. This method is invoked by the library for every new session
(which corresponds to a new user accessing your web application). The
request arguments (as part of the
WEnvironment
object) are passed to
this createApplication()
function, and may be used to customize the
application or authenticate the user. See also Application
bootstrap for details on the application bootstrap method.
At all times, the current WApplication
instance is accessible using
the static method
WApplication.getInstance()
,
and is useful to inspect startup arguments and settings using
getEnvironment()
, to
set or change the application title using
setTitle()
,
to specify a locale using
setLocale()
,
and many other application-wide settings. Access to this instance is
implemented using thread local storage.
A session exits when the user browses away from the application, when
WApplication.quit()
is called,
or when the servlet container is shut down. From this moment on, your
widget tree will be waiting to be garbage collected. Therefore, you
should release auxiliary resources held by your widgets or application
in the finalize()
method of these objects.
During the lifetime of a session, the controller implemented by
WtServlet
will handle and interpret requests, invoke event handling
code, and render updates and changes to your widget tree. This is
strictly an internal affair of the library, except that it allows you
to scope the use of resources to a single request. To that extent, the
library provides a central entry point for doing things like acquiring
and releasing database connections and transactions, or have a single
place for handling internal application errors. Each request is
handled from within
WApplication.notify()
,
and by reimplementing this method you may control resource usage
during each request.
By default, servlet containers are configured to use cookies for
session tracking. This configuration disables a user to have multiple
concurrent sessions of the same application running in the same
browser. In order to achieve such behavior, you should configure your
servlet container to use URL session tracking instead. The
ServletInit
ServletContextListener
should take care of this, and can be configured as
a listener with the following XML in your web.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<listener>
<listener-class>eu.webtoolkit.jwt.ServletInit</listener-class>
</listener>
</web-app>
2.4. Event handling
To respond to user interactivity events, or in general to communicate events from one widget to any other, JWt uses a signal/listener system, which is a popular implementation of the Observer pattern.
Depending on the number of objects your signal propagates to
listeners, you need to use Signal
,
Signal1
,
Signal2
, … , Signal6
as a signal object.
Listeners implement the corresponding listener class, and can be added
to a signal using addListener()
. Because the listener interface only
requires the implementation of a single method, it is convenient to
use a lambda function or anonymous inner class to implement it. The
following example is taken from the hello example.
nameEdit = new WLineEdit(getRoot());
greeting = new WText(getRoot());
WPushButton button = new WPushButton("Greet me.", getRoot());
button.clicked().addListener(this, () -> {
greeting.setText("Hello there, " + nameEdit.getText());
});
The library defines several user event signals on various widgets, and it is easy and convenient to add signals and listeners to widget classes to communicate events and trigger callbacks.
Event signals (EventSignal
) are signals
that may be triggered internally by the library to respond to user
interactivity events. The abstract base classes
WInteractWidget
and
WFormWidget
define most of these event
signals.
2.5. Server push
By default, updates to the user interface are possible only at startup, during any event (in a signal listener),
or at regular time points using WTimer
. This is the normal JWt event loop.
In some cases, one may want to modify the user interface from a second thread, outside the event loop.
While this may be worked around by the WTimer
, in some cases, there are
bandwidth and processing overheads associated which may be unnecessary, and which create a trade-off
with time resolution of the updates.
When "server push" (also called "comet" in AJAX terminology) is enabled, widgets may be modified,
created, or deleted outside of the event loop (e.g. in response to execution of another thread),
and these changes are propagated by calling triggerUpdate()
.
For more information, please refer to the reference documentation of
WApplication.enableUpdates()
.
This function enables or disables server push.
You can find a feature example on server push and a more elaborate simplechat
example in the JWt source code.
2.6. Optimizing client-side event handling
By default, JWt performs all event processing server side. Every connected event signal will cause the web browser to communicate with the servlet container in order to invoke the listener’s implementation, and visual changes will be updated in the web page.
However, JWt offers several options for incorporating client-side event handling. This may in general increase responsiveness of the application since the user gets an instant feed-back and the communication delay is avoided.
JWt provides a number of mechanisms to integrate JavaScript code with Java:
-
using
JSlot
, you can specify the JavaScript for a listener, when connected to anEventSignal
orJSignal
. -
using
JSignal
, you can emit a Java signal from JavaScript code, using a JavaScript functionWt.emit()
. -
using
WApplication.doJavaScript()
, you can call JavaScript code directly as part of event handling.
2.7. Application bootstrap
A JWt application may support both plain HTML and Ajax-enabled user agents. When a first request is made for a new session, there is no way of knowing whether the agent supports Ajax (and has it enabled). The bootstrap procedure therefore has two strategies of making the choice between a plain HTML and Ajax-enabled application mode.
2.7.1. Default bootstrap
In the default bootstrap mode, for the normal case, a small bootstrap HTML file is served, which detects presence of AJAX (and various other environment properties). When no JavaScript support is available, it automatically redirects the user to a plain HTML version of the application.
In this mode, the application is not started until the library has
determined AJAX support, which is made available in
WEnvironment.hasAjax()
which
is passed to the application constructor.
In some special cases, this bootstrap is skipped and a plain HTML version is served right away. This is for user agents that are identified as spider bots, or user agents which are configured to not support AJAX (well), see the user agents configuration setting.
There are some drawbacks to this bootstrap method:
-
the redirection without JavaScript support may not be supported by all user agents. To handle this, the bootstrap page also shows a link and a redirect message (see the redirect message configuration setting), which may confuse new users.
-
there is an additional round trip before any contents are rendered.
-
for an AJAX user interface, all contents will be loaded through JavaScript. This has a drawback that some 3rd party JavaScript libraries do not support being loaded on-demand.
2.7.2. Progressive bootstrap
While the default bootstrap already honors the principle of graceful degradation, progressive bootstrap implements this using the principle of progressive enhancement.
This bootstrap method may be enabled with the progressive bootstrap configuration setting.
This bootstrap method will initially assume that the user agent is a
plain HTML user-agent and immediately create the application (with
WEnvironment.hasAjax()
always returning false
). The initial response will contain the initial
page suitable for a plain HTML user agent.
JavaScript embedded in this page will sense for AJAX support and
trigger a second request which progresses the application to an AJAX
application (without repainting the entire user interface). To that extent,
it will change
WEnvironment.hasAjax()
to
return true
, and invoke
WApplication.enableAjax()
which in turn propagates
WWidget.enableAjax()
through the widget
hierarchy. This upgrade happens in the background, unnoticeable to the
user.
This mitigates disadvantages associated with the default bootstrap, but has the
drawback of requiring consistent enableAjax()
implementations and requiring more
server side processing.
2.8. Painting
JWt provides a vector graphics painting system which, depending on
browser support, uses one of three different methods to paint the
graphics (inline SVG, HTML 5 <canvas>
element, or server side rendered PNG).
Vector graphics has as benefit a lower bandwidth usage, which is independent
of the image size and quality, and can be embedded within the HTML,
avoiding an additional round trip. To use the paint system, you need
to specialize WPaintedWidget
and use
a WPainter
to paint the contents of the
widget inside its
paintEvent()
.
The charting library is built on top of this painting infrastructure.
2.9. Deployment
JWt uses the Java Servlet API, and thus JWt applications are all
compatible with commonly used servlet containers (Tomcat, Jetty, …)
and JEE application servers (JBoss, Glassfish, …). The most common
way to deploy a JWt application is by packaging the application as a
.war
file and deploying it into a servlet container. Because JWt
applications are plain Java applications, without the need for code
generation or XML editing, JWt application development can be
conveniently done from your favorite Java IDE. See the
Getting Started section for hints on how to create
war files and develop JWt applications from within Eclipse.
2.10. Configuration
JWt can be configured by changing values in the
Configuration
object accessible via
the
WtServlet.getConfiguration()
method.
3. Getting started
In this section, we will go through the steps to download and install JWt, and deploy and run the examples that come bundled with it. We will detail the procedure for the ubiquitous "Hello world" example, but you can follow the same steps for each of the examples.
The examples that come with the library are each a self-contained project that only requires the JWt library (and its dependencies) to get going. You could structure your own projects in the same way as each of these example projects, but it is up to you: the library does not enforce a particular layout of your web application, it simply acts as a library (rather than a "framework"). |
We present two tracks for running your first JWt application: from within the Eclipse IDE or from the command line. You will need to download the JWt distribution which contains the library and its dependencies, from the homepage’s download section.
3.1. "Hello world" from within Eclipse
The library and all of the examples include a .project
file which is
used by Eclipse to manage the build process. For deploying the web
application during development, we recommend using the
Eclipse Jetty
plugin.
3.1.1. Requirements
To run the "Hello world" example with Eclipse you will need to install the following:
-
Java Development Kit (version 11 or later), e.g. from https://adoptium.net/
-
Eclipse IDE from http://www.eclipse.org/downloads/
-
Eclipse Jetty plugin from https://eclipse-jetty.github.io/
3.1.2. Importing into Eclipse
-
Extract the JWt source distribution to a directory on your system.
-
Start Eclipse.
-
Right-click inside your "Project Explorer" and select "Import…".
-
Select "General" → "Existing Projects into Workspace" and click "Next".
-
Click "Browse".
-
Navigate to the directory where the JWt source distribution was extracted to. Select the
examples/hello
directory and click "OK".You can import multiple examples at once by selecting the
examples
directory instead, and selecting multiple projects in the next step. -
The hello project will be visible and selected, click "Finish".
-
The imported
jwt-hello
project becomes visible in the "Project Explorer" tree.There are however still build errors as the JWt library and dependencies are still missing.
-
We have provided an ant build script that copies the JWt library and dependencies in the
lib
folder and also builds a.war
file.Right-click on the
build.xml
, "Run As" → "Ant Build". When this is your first example, the build script will also build the JWt library itself. -
"Refresh" your jwt-hello project. The example is now built and ready to be run.
3.1.3. Running
Below, we use the Eclipse Jetty plugin. You may also use any other
JEE application server (with integration in Eclipse) to which you
deploy the dist/jwt-hello.war
file which has also been
built by the ant
tool.
-
Open the 'Run' → 'Run Configurations…' dialog.
-
Create a new "Jetty Webapp".
-
Click "Run" (your configuration settings will be saved).
-
The servlet container starts listening on port 8080
-
Open your web browser and point it to http://localhost:8080/.
3.2. "Hello world" from the command line using Ant
To build the library and examples, we will be using ant, and for deployment we will use the lightweight Jetty servlet container.
3.2.1. Requirements
To run the "Hello world" example from the command line you will need to install the following:
-
Java Development Kit (version 11 or later), e.g. from https://adoptium.net/
-
Ant from https://ant.apache.org/
-
Jetty (version 9, preferably 10, or later) from https://www.eclipse.org/jetty/. In the end, any servlet container or application server will do. These instructions however assume Jetty.
3.2.2. Building
-
Open you command line console.
-
Extract the JWt source distribution to a directory on your system, and navigate to this directory.
-
Enter the
examples/hello
directory and runant
:$ cd examples/hello $ ant
This will build
dist/jwt-hello.war
, handling all of the dependencies including JWt itself.
3.2.3. Running
-
We’ll call the location where you installed Jetty
$JETTY_HOME
. -
Create a
jetty-base
directory. We’ll call this$JETTY_BASE
. -
From the
$JETTY_BASE
directory, runjava -jar $JETTY_HOME/start.jar --add-module=server,http,deploy
-
Copy the
jwt-hello.war
to$JETTY_BASE/webapps/hello.war
. -
Start Jetty:
java -jar $JETTY_HOME/start.jar
-
Open your preferred web browser and navigate to http://localhost:8080/hello/.
3.3. A closer look at a JWt web application project structure
Setting up a JWt project does not involve much, since JWt acts as a
Java library (on top of the Java servlet API). After running ant
in
the JWt source distribution folder, all required jar files can be
found in the dist
folder. To use JWt in your project,
you need only to include these jar files into your project library
path.
The start point of your application is defined by extending
WtServlet
which implements a Java servlet, and provides an entry
point to a JWt application. An example of this is
eu.webtoolkit.jwt.examples.hello.HelloMain
.
To deploy the web application you need to provide a WebRoot/WEB-INF/web.xml
configuration file which binds the servlet to a context path:
WebRoot/WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<listener>
<listener-class>eu.webtoolkit.jwt.ServletInit</listener-class>
</listener>
<servlet>
<servlet-name>Hello</servlet-name>
<display-name>JWt Hello World example</display-name>
<description>JWt Hello World example</description>
<servlet-class>eu.webtoolkit.jwt.examples.hello.HelloMain</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Hello</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>