Creating an Embedded Vaadin Application Tutorial
In this section, we demonstrate how to create a basic embedded application: that is, an application that can be embedded in another page.
Note
| The technology you use to create the host page is irrelevant: you can use Vaadin or non-Vaadin server-side Java technologies, like JSP, Thymeleaf, or servlet, or even a basic static HTML page. |
In order to be able to embed a Vaadin application, you need to create:
-
An entry point. This is achieved by creating a
WebComponentExporter
for the VaadinComponent
you want to embed, and -
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.
Our scenario uses:
-
A single custom servlet to handle the main application logic.
-
The servlet displays primarily static content.
-
The content differs depending on whether a user is logged in or not.
-
-
An embeddable Vaadin
Component
to implement a login form.
Note
|
VaadinServlet forms part of the application, but its mapping differs from the main servlet mapping.
|
You can start with a clean project, or start building on top of the Vaadin Base Starter (download the "Plain Java Servlet" version).
-
Create the
MainAppServlet
servlet class.This servlet serves the static page which embeds our login 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='text/javascript' " + "src='/vaadin/VAADIN/build/webcomponentsjs/" + "webcomponents-loader.js'></script>"); 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>"); } } }
Let’s analyze the HTML content generated by
MainAppServlet
:-
This line loads the web component polyfill:
<script type='text/javascript' src='/vaadin/VAADIN/build/webcomponentsjs/webcomponents-loader.js'> </script>
-
This line loads the web component:
<script type='module' src='/vaadin/web-component/login-form.js'> </script>
-
Both script
src
attributes start with/vaadin/
. This is the URI to use to map to the Vaadin servlet (see step 3 below). -
The second part the link URI,
/web-component/login-form.js
, is the standard URI to use to import the web component. It consists of the hard-codedweb-component
part, followed bylogin-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 our example must be
"login-form"
. This name must be used in both thesuper
constructor of the exporter (seeLoginFormExporter
in step 5 below) and the HTML code where the web component is inserted. In our 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
andpwdlbl
. These values are passed from the HTML to a web component instance.NoteIf embedding applications is targeted towards very specific browsers, the polyfill is not needed. For example Chrome and Firefox do not need the polyfill while Edge does.
-
-
-
Create the
UserService
class. This class contains the authentication logic and is shared between theMainAppServlet
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(); } } }
-
@WebServlet(urlPatterns = { "/vaadin/*" }) public class WebComponentVaadinServlet extends VaadinServlet { }
-
As mentioned above, the
/vaadin/*
mapping allows theVaadinServlet
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. -
If you are operating in compatibility mode, see Embedding Applications in Compatibility and Production Mode for more information about implementing the
VaadinServlet
.NoteIf you deploy your web component exporter(s) as a standalone WAR application, an explicit servlet registration is unnecessary. A servlet instance is registered automatically with the "/*"
mapping.
-
-
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
andButton
. -
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 that now shows content specific to authenticated users. There are various ways to do this:-
Execute JavaScript directly from your Java code and set the location to
"/example"
:getUI().get().getPage().executeJs("window.location.href='/example'");
. -
Use a solution similar to this example: design the component code so that its logic is isolated and it does not need to know anything about the embedding context. This method allows you to completely decouple the embedded component logic from the application that uses it. In this example, the
addLoginListener
method allows you to register a listener which is called in thefireLoginEvent
method.
-
-
-
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 constructorsuper("login-form");
. -
The
addProperty
method defines the component properties (userlbl='Username' and `pwdlbl='Password'
) to receive values from the HTML element to the web component instance. In this example we declare the labels for user name field and password field via HTML, instead of hard-coding them in theLoginForm
component class. -
LoginFormExporter
class implements the abstract method,configureInstance
, which registers a login listener. -
The login listener fires a client-side
"logged-in"
event, using thewebcomponent.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>
inMainAppServlet
. This is thelog-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 simply redirects the page to the"/example"
location.
-
-
Run the application with
mvn jetty:run
. Once jetty has started, navigate to http://localhost:8080/example.-
Type in login information and click on "Login" button
-
Username: admin
-
Password: admin
-
-
B1BE4F4B-FBAB-4108-B25F-2AB4EA36275A