Creating an Embedded Vaadin Application
It’s possible to create a basic embedded application: that is, an application that can be embedded in another page.
Incidentally, the technology you use to create the host page is irrelevant. You can use Vaadin or non-Vaadin server-side Java technologies, such as JSP, Thymeleaf, or servlet, or even a basic static HTML page.
To embed a Vaadin application, you need to do two things. First, you’ll need to create an entry point. This is achieved by creating a WebComponentExporter
for the Vaadin Component
you want to embed.
Second, you’ll need a VaadinServlet
to handle requests to your web component. The servlet can be declared in a non-Vaadin application, or deployed separately as a standalone WAR
file.
This scenario uses a single custom servlet to handle the main application logic: the servlet displays primarily static content; and the content differs, depending on whether a user is logged in.
It also uses an embeddable Vaadin Component
to implement a log-in form.
Note
|
VaadinServlet forms part of the application, but its mapping differs from the main servlet mapping.
|
Create an Entry Point
You can start with a clean project, or start building on top of the Vaadin Base Starter (download the "Plain Java Servlet" version).
First, create the MainAppServlet
servlet class. This servlet serves the static page which embeds the log-in form as a web component.
@WebServlet(urlPatterns = {"/example"})
public class MainAppServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
Object authToken = req.getSession().getAttribute("auth_token");
boolean isAuthenticated = authToken != null;
try (PrintWriter out = response.getWriter()) {
out.println("<!DOCTYPE html>");
out.println("<html><head>");
out.println("<meta http-equiv='Content-Type' content='text/html; "
+ "charset=UTF-8'>");
if (!isAuthenticated) {
out.println("<script type='module' src='/vaadin/web-component"
+ "/login-form.js'></script>");
out.println("<script type='text/javascript' "
+ "src='/log-in.js' defer></script>");
}
out.println("</head><body>");
if (isAuthenticated) {
out.println("<h1>Welcome "
+ UserService.getInstance().getName(authToken)
+ "</h1>");
} else {
out.println("<login-form userlbl='Username' pwdlbl='Password'>"
+ "</login-form>");
}
out.println("</body>");
out.println("</html>");
}
}
}
The HTML content generated by MainAppServlet
includes this line that loads the web component:
<script type='module'
src='/vaadin/web-component/login-form.js'>
</script>
The script src
attribute starts with /vaadin/
. This is the URI to use to map to the Vaadin servlet (see step 3 below).
The second part of the link URI,/web-component/login-form.js
, is the standard URI to use to import the web component. It consists of the hard-coded web-component
part, followed by login-form.js
, which is the web component file. The web component file is generated by Vaadin, based on the configuration set in the exporter.
The name of the web component in this example must be "login-form"
. It must be used in both the super
constructor of the exporter (see LoginFormExporter
in step 5 below) and the HTML code where the web component is inserted.
In this example, this is right under the <body>
tag:
<login-form userlbl='Username' pwdlbl='Password'>
</login-form>
The "login-form"
web component has two properties: userlbl
and pwdlbl
. These values are passed from the HTML to a web component instance.
Note
|
The generated "login-form" element is a web component which wraps the original LoginForm HTML representation element ("div" , see below). This element is inside wrapper’s shadow root and isn’t available outside it. Hence, its styles don’t leak to the page, and global page styles don’t affect the styles of the embedded component.
|
Handling Requests
Next, you’ll need to create the UserService
class. This class contains the authentication logic and is shared between the MainAppServlet
and the web component class (created in step 5 below). You can use any interface and implementation you like.
This example is a custom stub implementation and is provided as a reference.
public final class UserService {
private static final UserService INSTANCE =
new UserService();
private UserService(){
}
public static UserService getInstance() {
return INSTANCE;
}
public String getName(Object authToken) {
return "Joe";
}
public Optional<Object> authenticate(String user,
String passwd) {
if ("admin".equals(user) &&
"admin".equals(passwd)) {
return Optional.of(new Object());
} else {
return Optional.empty();
}
}
}
Register the VaadinServlet
Now you’ll need to register the VaadinServlet
. Here’s how you might do that:
@WebServlet(urlPatterns = { "/vaadin/*" }, asyncSupported = true, loadOnStartup = 1)
public class WebComponentVaadinServlet extends VaadinServlet {
}
As mentioned above, the /vaadin/*
mapping allows the VaadinServlet
to handle web component requests. You can use any URI, but be sure to use the same URI in the mapping and in the import declaration.
Note
|
If you deploy a web component exporter as a standalone WAR application, an explicit servlet registration is unnecessary. A servlet instance is registered automatically with the "/*" mapping.
|
You’ll also need to create the LoginForm
component class:
public class LoginForm extends Div {
private TextField userName = new TextField();
private PasswordField password =
new PasswordField();
private Div errorMsg = new Div();
private String userLabel;
private String pwdLabel;
private FormLayout layout = new FormLayout();
private List<SerializableRunnable> loginListeners =
new CopyOnWriteArrayList<>();
public LoginForm() {
updateForm();
add(layout);
Button login = new Button("Login",
event -> login());
add(login, errorMsg);
}
public void setUserNameLabel(
String userNameLabelString) {
userLabel = userNameLabelString;
updateForm();
}
public void setPasswordLabel(String pwd) {
pwdLabel = pwd;
updateForm();
}
public void updateForm() {
layout.removeAll();
layout.addFormItem(userName, userLabel);
layout.addFormItem(password, pwdLabel);
}
public void addLoginListener(
SerializableRunnable loginListener) {
loginListeners.add(loginListener);
}
private void login() {
Optional<Object> authToken = UserService
.getInstance()
.authenticate(userName.getValue(),
password.getValue());
if (authToken.isPresent()) {
VaadinRequest.getCurrent()
.getWrappedSession()
.setAttribute("auth_token",
authToken.get());
fireLoginEvent();
} else {
errorMsg.setText("Authentication failure");
}
}
private void fireLoginEvent() {
loginListeners.forEach(
SerializableRunnable::run);
}
}
The example uses several Vaadin components: FormLayout
, TextField
, PasswordField
and Button
.
The code takes care of authentication and sets an authentication token in the HttpSession
, which makes it available while the session is live.
Because the main application servlet uses the same HttpSession
instance, it changes behavior and redirects authenticated users to the main servlet, which now shows content specific to authenticated users.
You can do this in various ways: One way would be to execute JavaScript directly from your Java code and set the location to "/example"
: getUI().get().getPage().executeJs("window.location.href='/example'");
. You could also use a solution similar to this example: design the component code so that its logic is isolated and it doesn’t need to know anything about the embedding context. This method allows you to decouple the embedded component logic completely from the application that uses it.
In this example, the addLoginListener()
method allows you to register a listener which is called in the fireLoginEvent()
method.
Export LoginForm Component
The final step is to export the LoginForm
component as an embeddable web component using the web component exporter.
public class LoginFormExporter
extends WebComponentExporter<LoginForm> {
public LoginFormExporter() {
super("login-form");
addProperty("userlbl", "")
.onChange(LoginForm::setUserNameLabel);
addProperty("pwdlbl", "")
.onChange(LoginForm::setPasswordLabel);
}
@Override
protected void configureInstance(
WebComponent<LoginForm> webComponent,
LoginForm form) {
form.addLoginListener(() ->
webComponent.fireEvent("logged-in"));
}
}
The exporter defines its tag name as "login-form"
by calling the super constructor super("login-form")
. LoginFormExporter
constructor must be public. Otherwise, Vaadin won’t be able to instantiate it.
The addProperty()
method defines the component properties (userlbl='Username' and `pwdlbl='Password'
) to receive values from the HTML element to the web component instance. This example declares the labels for the user name field and password field via HTML, instead of hard-coding them in the LoginForm
component class.
The LoginFormExporter
class implements the abstract method, configureInstance()
, which registers a log-in listener.
The login listener fires a client-side "logged-in"
event, using the webcomponent.fireEvent()
method. The main application needs to handle this event somehow.
The custom event is handled by the JavaScript file declared via the line <script type='text/javascript' src='log-in.js'></script>
in MainAppServlet
.
This is the log-in.js
file content — place the log-in.js
under ./src/main/webapp/
:
var editor = document.querySelector("login-form");
editor.addEventListener("logged-in", function(event) {
window.location.href='/example';
});
The embedding servlet uses the API provided by LoginForm
via a custom event and adds an event listener for the event. The listener redirects the page to the "/example"
location.
Run the application with mvn jetty:run
. After Jetty has started, navigate to http://localhost:8080/example. Then type in the log-in information and click on the "Login" button: admin
for both user and password.
0E2FEAC2-C4A1-4CB9-9859-B6E890A937C0