martes, 16 de abril de 2019

17. Vaadin Loginform (2). Adding style

updated on April 17, 2019

0. Introduction


It is important to give style to the components. Vaadin, sometimes is rejecting to assing some styles to the components, so it is important to test it.

1. Using CSS

We will put the CSS files in this folder:

  • Deployed Resources/webapp/frontend/css


2. The @HtmlImport annotation of the class

We will annotate the use of the "CSS file" in the LoginForm class with @HtmlImport(css/LoginForm.css).


1
2
3
4
5
6
@Route("")
@PageTitle("Login")
@HtmlImport("css/LoginForm.css")
@SuppressWarnings("serial")
public class LoginForm extends VerticalLayout implements II18nChange{
 

Here is the CSS code of LoginForm.css


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
!-- Remember to import custom-style, which is included in the Polymer package -->
<link rel="import"
      href="../bower_components/polymer/lib/elements/custom-style.html">


<custom-style>
  <style>
   .edu-login{
    
     /*
     height: 100%;
     margin: 0;
    padding: 0;
    
    */
    max-width: 600px; 
    max-height: 600px;
    color: var(--lumo-primary-text-color);
    /** Water image: Chrome needs url reltives from app while other url are relatives from css file */
     background: linear-gradient(to bottom right, rgba(255,255,255,.3), rgba(255,255,255,.99)),
                 url("../img/enterprise/enterprise02.jpeg"), /* GOOD in eclipse; BAD in chrome*/ 
                 url("frontend/img/enterprise/enterprise02.jpeg"); /* BAD in eclipse; GOOD in chrome*/     background-position: center; /* Center the image */
        background-repeat: no-repeat; /* Do not repeat the image */
        background-size: cover; /* Resize the background image to cover the entire container */            
    }
    .edu-login-header{
     width: 100%;
     border-width:5px;  
        border-bottom-style: solid;
        border-color: DodgerBlue;
        /*box-shadow: 10px 10px 5px LightGray;*/
        
        /*box-shadow: 0px 1px 3px Blue;*/
    }
    .edu-login-lang{
     width: 100%;
     min-width: 300px;
        /*box-shadow: 10px 10px 5px gray;*/
    }
    .edu-login-login{
     
     /*background: LightYellow;*/
        /*box-shadow: 5px 5px 3px LightGray;*/
    }
    .edu-login-title{
     /*background: yellow;*/
     width: 100%;
    }
       
    .edu-login-buttons{
     /*background: yellow;*/
     /*color: var(--lumo-primary-text-color);*/
     width: 100%;
    }
    
    /*****************************************/
    /*   MAIN VIEW                           */
    /*****************************************/
    
    .edu-main-header {
     width: 100%;
     background: yellow;
     margin: 0px;
     border: 0px;
     padding: 0px;
    }
    .edu-main-v1 {
     background: gray;
     margin: 0px;
     border: 0px;
     padding: 0px;
    }
    .edu-main-h11 {
     background: green;
     margin: 0px;
     border: 0px;
     padding: 0px;
    }
    .edu-main-h12 {
     background: beige;
     margin: 0px;
    }
   
    /*****************************************/
    
    /* Stack login-screen vertically on narrow screen */
    /*
    @media (max-width: 800px) {
     
    }*/
    
    
  </style>
</custom-style>


3. Setting stile to components

There are some ways of setting styles to components, some methods are only allowed to certain component types:

  1. component.setClassName("edu-login-header"). In this case, the style is in line 27.
  2. component.getStyle().set("font-size", "150%").set("text-align", "center"). It is used in a label. You can concatenate "sets".
  3. component.setWidth("15em"). In this casse it is applied to a combo.
If the setClassName() method of the components is ignored by Vaadin then you should try the options 2 and 3.


4. Analyzing the Lofinform.css file


Here is a snippet of code showing an image in the background with transparency and gradient. Note that it did not work well on all browsers, so some alternatives have been used to offset this handicap.


1
2
3
4
5
6
7
/** Water image: Chrome needs url reltives from app while other url are relatives from css file */
     background: linear-gradient(to bottom right, rgba(255,255,255,.3), rgba(255,255,255,.99)),
                 url("../img/enterprise/enterprise02.jpeg"), /* GOOD in eclipse; BAD in chrome*/ 
                 url("frontend/img/enterprise/enterprise02.jpeg"); /* BAD in eclipse; GOOD in chrome*/
     background-position: center; /* Center the image */
        background-repeat: no-repeat; /* Do not repeat the image */
        background-size: cover; /* Resize the background image to cover the entire container */            

The rest of the details include common tags as : margin, border....

5. The LoginForm class with style.
Here is the new code of the class


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
package openadmin.ui;

import javax.annotation.PostConstruct;
import javax.inject.Inject;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Html;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.dependency.HtmlImport;
import com.vaadin.flow.component.html.Image;
import com.vaadin.flow.component.html.Label;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.PasswordField;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;

import openadmin.session.SessionData;
import openadmin.utils.VaadinUtils;


@Route("")
@PageTitle("Login")
@HtmlImport("css/LoginForm.css")
@SuppressWarnings("serial")
public class LoginForm extends VerticalLayout implements II18nChange{
 private TextField userName;
 private PasswordField password;
 private Button login;
 private LocaleCombo langCombo;
 private Label lblUserName;
 private Label lblPassword;
 private Label lTitle;
 private Image imageLang;
 
 @Inject
 private SessionData sesData;
 
 public LoginForm() {
 }
 
 /**
  * Initialization
  */
 @PostConstruct
 private void init() {
  setClassName("edu-login");
  add(getHeaderPanel());
  add(getLangSelectorPanel());
  add(getLoginPanel());
 }
 
 
 /** Enterprise header **/
 private HorizontalLayout getHeaderPanel() {
 
  HorizontalLayout headerPanel = new HorizontalLayout();
  headerPanel.setClassName("edu-login-header");
  
  Image logo = new Image("frontend/img/enterprise/enterprise05.jpeg", "logo");
  headerPanel.setVerticalComponentAlignment(FlexComponent.Alignment.CENTER, logo);
  headerPanel.add(logo);
  
  HorizontalLayout title = new HorizontalLayout();
  title.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
  
  lTitle=new Label(getTranslation ("main.enterprise"));
  lTitle.getStyle()
   .set("font-size", "150%")
   .set("text-align", "center");
  
  headerPanel.setVerticalComponentAlignment(FlexComponent.Alignment.CENTER, lTitle);
  title.add(lTitle);
  title.setClassName("edu-login-title");
  headerPanel.add(title);
  return headerPanel;
 }

 private HorizontalLayout getLangSelectorPanel() {
  
  HorizontalLayout langSelPanel = new HorizontalLayout();
  langSelPanel.setClassName("edu-login-lang");
  langSelPanel.setJustifyContentMode(FlexComponent.JustifyContentMode.END);
  
  // 1. Language ComboBox.
  langCombo = new LocaleCombo(sesData, this);
  langCombo.setWidth("15em");
  langCombo.setClassName("edu-langcombo");
  langCombo.getStyle().set("font-weight","bold");
  
  
  langSelPanel.setVerticalComponentAlignment(FlexComponent.Alignment.CENTER, langCombo);
  langSelPanel.add(langCombo);
    
  // 2.Flag Icon
  imageLang = new Image("frontend/icon/flags/" + sesData.getLangEnum().name() + ".png", "language");
  //imageLang = new Image("frontend/icon/flags/" + lEnum.name() + ".png", "language");  // Jetty
  imageLang.getStyle().set("vertical-align", "middle").set("padding-left", "25");
  
  
  langSelPanel.setVerticalComponentAlignment(FlexComponent.Alignment.CENTER, imageLang);
  langSelPanel.add(imageLang);

  return langSelPanel;
 }
 
 private Component getLoginPanel() {
         
        VerticalLayout loginPanel= new VerticalLayout();
        loginPanel.setClassName("edu-login-login");
        
        VerticalLayout loginForm = new VerticalLayout();
        loginForm.setWidth("300px");
  
        // 1. User
        lblUserName = new Label(getTranslation ("login.user"));
  userName=new TextField();
  userName.setValue("admin");
  userName.setWidth("5em");
  loginForm.add(lblUserName, userName);
   
  //2. Password
  lblPassword = new Label(getTranslation("login.password"));
  password = new PasswordField();
  password.setWidth("15em");
  loginForm.add(lblPassword, password);
  
  
  //3. Line separation
  loginForm.add(new Html("<br/>"));
  
  //4. Buttons layout
  HorizontalLayout buttonsLay = new HorizontalLayout();
  buttonsLay.setClassName("edu-login-buttons");
  buttonsLay.setJustifyContentMode(FlexComponent.JustifyContentMode.END);
  loginForm.add(buttonsLay);
  
  buttonsLay.add(login = new Button(getTranslation("login.login"), VaadinIcon.SIGN_IN.create()));
  
  //5. Login button
  login.addClickListener(event -> login()); //assign action to button
  login.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_PRIMARY);
  
  //6 Keyboard event listeners 
  loginForm.getElement().addEventListener("keypress", event -> login()).setFilter("event.key == 'Enter'");
  loginPanel.setHorizontalComponentAlignment(FlexComponent.Alignment.CENTER, loginForm);
  loginPanel.add(loginForm);
  
  return loginPanel;
 }
 
 /**
  * Try to log in if credentials are correct 
  */
 private void login() {
  if (userName.getValue().length()<1 || password.getValue().length()<1) {
   showNotification("login.invalid_credentials", 2000);
   userName.focus();
  
  // usr & password credentials 
  }else if (userName.getValue().equals("user") && password.getValue().equals("password")) {
   VaadinUtils.setSessionAttribute("CURRENT_USER", userName.getValue());
   getUI().get().navigate("main");
   
  }else {
   showNotification("login.invalid_credentials", 2000);
   userName.focus();
  }
 }

 /**
  * Show notification 
  * @param i18nKey
  * @param duration
  */
 public void showNotification(String i18nKey, int duration) {
  String translatedMessage=this.getTranslation(i18nKey);
  Notification not=new Notification(translatedMessage);
  not.setDuration(duration);
  not.open();
 }
 /**
  * Change labels according to Locale
  */
 @Override
 public void changeI18nLabels() {
  lTitle.setText(getTranslation ("main.enterprise"));
  lblUserName.setText(getTranslation("login.user"));
  lblPassword.setText(getTranslation("login.password"));
  login.setText(getTranslation("login.login"));
  imageLang.setSrc("frontend/icon/flags/" + sesData.getLangEnum().name() + ".png");
  //imageLang.setSrc("frontend/icon/flags/" + lEnum.name() + ".png");  //Jetty
 }
}

and here is the output.


Happy codding

lunes, 15 de abril de 2019

16. Vaadin LoginForm (1). Basic components

updated on April 16, 2019

0. Introduction


A simple login form can have basically:
  • A user name input field
  • A password input field
  • A submit button
  • A component for showing messages
  • A component for selecting the language
We are calling this class "LoginForm" and this name must be the same as the one defined in then openadmin.listeners.LoggedInListener class for redirecting to the user that has not been logged in. See this previous post.

1. Designing the visual interface



Here is a simple sketch of the login form

+---------------------------------------------------------+
| +--------+                                              |
| | LOGO   |     1                ENTERPRISE NAME         |
| |        |                                              |
| +--------+                                              |
+---------------------------------------------------------+
|                           +---------------------------+ |
|                2          | Language selector combo   | |
|                           +---------------------------+ |
+---------------------------------------------------------+
|                                                       |
|                 +-------------------+                   |
|           User: |                   |                   |
|                 +-------------------+                   |
|                                                         |
|                                                         |
|                 +-------------------+                   |
|       Password: |                   |                   |
|                 +-------------------+                   |
|                                                         |
|                         +-------------------+           |
|                         |  Login Button     |           |
|                         +-------------------+           |
|                                                         |
|  +--------------------------------------------------+   |
|  |  Notification messages                           |   |
|  +--------------------------------------------------+   |
|                                                         |
+---------------------------------------------------------+

We are considering 3 containers indicated in yellow colour:

  1. Header panel
  2. Language selector panel
  3. Login panel

2. Image resources

The image resources (photos of the enterprise) are in this folder. If not exists the route from webapp folder you should create it.

  • Deployed Resources/webapp/frontend/img/enterprise
The images are taken from a "Whatsapp group" and are a protest against the citriculture problems in Valencia (Spain). It is a simple example. The images are the same but have different size.





About the flag images, we have talked about in a previous entry. They are in this folder:
  • Deployed Resources/webapp/frontend/icon/flags
3. i18n resources
They are in this folder. If not exists i18n folder you should create it.
  • src/main/resources/i18n
There are 2 groups of resources:
  1. login
  2. main

the contents of the English version of login is:


user = User
password = Password
login = Login
invalid_credentials = Invalid credentials

and the contents of the English version of main is:


enterprise=The Death of Agriculture in Valencia 


3. LoginForm component

Take into account that:

  1. Is annotated by @Route("") as it is the first route Vaadin looks for.
  2. Extends a VerticalLayout
  3. Implements II18nChange (created in a previous post), to allow changing labels affected by the Locale. The method it implements is changeI18nLables() 
  4. It has 3 parts (header panel, language selector panel and login panel)
  5. The language selector panel has a component that was created in the previous post that select the language and changes all labels content affected by the language change.
  6. The login button executes the login() procedure to test if the user can log in.
  7. The keyboard key "enter" also activates the login() procedure
  8. If the user is logged in, it is redirected to a view that is annotated by  @Route("main").
  9. The valid user is "user" and the password is "password"
Here is the code


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
ackage openadmin.ui;

import javax.annotation.PostConstruct;
import javax.inject.Inject;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Html;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.html.Image;
import com.vaadin.flow.component.html.Label;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.PasswordField;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;

import openadmin.session.SessionData;
import openadmin.utils.VaadinUtils;


@Route("")
@PageTitle("Login")
@SuppressWarnings("serial")
public class LoginForm extends VerticalLayout implements II18nChange{
 private TextField userName;
 private PasswordField password;
 private Button login;
 private LocaleCombo langCombo;
 private Label lblUserName;
 private Label lblPassword;
 private Label lTitle;
 private Image imageLang;
 
 @Inject
 private SessionData sesData;
 
 public LoginForm() {
 }
 
 /**
  * Initialization of the 3 panels
  */
 @PostConstruct
 private void init() {
  add(getHeaderPanel());
  add(getLangSelectorPanel());
  add(getLoginPanel());
 }
 
 
 /** Enterprise header **/
 private HorizontalLayout getHeaderPanel() {
 
  HorizontalLayout headerPanel = new HorizontalLayout();
  
  
  Image logo = new Image("frontend/img/enterprise/enterprise04.jpg", "logo");
  headerPanel.setVerticalComponentAlignment(FlexComponent.Alignment.CENTER, logo);
  headerPanel.add(logo);
  
  HorizontalLayout title = new HorizontalLayout();
  title.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
  
  lTitle=new Label(getTranslation ("main.enterprise"));
  
  headerPanel.setVerticalComponentAlignment(FlexComponent.Alignment.CENTER, lTitle);
  title.add(lTitle);
  
  headerPanel.add(title);
  return headerPanel;
 }

 private HorizontalLayout getLangSelectorPanel() {
  
  HorizontalLayout langSelPanel = new HorizontalLayout();
  
  langSelPanel.setJustifyContentMode(FlexComponent.JustifyContentMode.END);
  
  // 1. Language ComboBox.
  langCombo = new LocaleCombo(sesData, this);
  
  langSelPanel.setVerticalComponentAlignment(FlexComponent.Alignment.CENTER, langCombo);
  langSelPanel.add(langCombo);
    
  // 2.Flag Icon
  imageLang = new Image("frontend/icon/flags/" + sesData.getLangEnum().name() + ".png", "language");
  //imageLang = new Image("frontend/icon/flags/" + lEnum.name() + ".png", "language");  // Jetty
  
  
  langSelPanel.setVerticalComponentAlignment(FlexComponent.Alignment.CENTER, imageLang);
  langSelPanel.add(imageLang);

  return langSelPanel;
 }
 
 private Component getLoginPanel() {
         
        VerticalLayout loginPanel= new VerticalLayout();
        VerticalLayout loginForm = new VerticalLayout();
  
        // 1. User
        lblUserName = new Label(getTranslation ("login.user"));
  userName=new TextField();
  userName.setValue("admin");
  loginForm.add(lblUserName, userName);
   
  //2. Password
  lblPassword = new Label(getTranslation("login.password"));
  password = new PasswordField();
  loginForm.add(lblPassword, password);
  password.setWidth("15em");
  
  //3. Line separation
  loginForm.add(new Html("<br/>"));
  
  //4. Buttons layout
  HorizontalLayout buttonsLay = new HorizontalLayout();
  buttonsLay.setJustifyContentMode(FlexComponent.JustifyContentMode.END);
  loginForm.add(buttonsLay);
  buttonsLay.add(login = new Button(getTranslation("login.login"), VaadinIcon.SIGN_IN.create()));
  
  //5. Login button
  login.addClickListener(event -> login()); //assign action to button
  
  //6 Keyboard event listeners 
  loginForm.getElement().addEventListener("keypress", event -> login()).setFilter("event.key == 'Enter'");
  loginPanel.setHorizontalComponentAlignment(FlexComponent.Alignment.CENTER, loginForm);
  loginPanel.add(loginForm);
  
  return loginPanel;
 }
 
 /**
  * Try to log in if credentials are correct 
  */
 private void login() {
  if (userName.getValue().length()<1 || password.getValue().length()<1) {
   showNotification("login.invalid_credentials", 2000);
   userName.focus();
  
  // user & password credentials 
  }else if (userName.getValue().equals("user") && password.getValue().equals("password")) {
   VaadinUtils.setSessionAttribute("CURRENT_USER", userName.getValue());
   getUI().get().navigate("main");
   
  }else {
   showNotification("login.invalid_credentials", 2000);
   userName.focus();
  }
 }

 /**
  * Show notification 
  * @param i18nKey
  * @param duration
  */
 public void showNotification(String i18nKey, int duration) {
  String translatedMessage=this.getTranslation(i18nKey);
  Notification not=new Notification(translatedMessage);
  not.setDuration(duration);
  not.open();
 }
 /**
  * Change labels according to Locale
  */
 @Override
 public void changeI18nLabels() {
  lTitle.setText(getTranslation ("main.enterprise"));
  lblUserName.setText(getTranslation("login.user"));
  lblPassword.setText(getTranslation("login.password"));
  login.setText(getTranslation("login.login"));
  imageLang.setSrc("frontend/icon/flags/" + sesData.getLangEnum().name() + ".png");
  //imageLang.setSrc("frontend/icon/flags/" + lEnum.name() + ".png");  //Jetty
 }
}

Here is the result in the browser


In the next post, we are adding style.

15. Vaadin i18n (2). Locale session attributes and Locale flag icons. A combo with flags!

updated on April 16, 2019

0. Introduction

We need to store Locale information in the session, and for selecting the locales, it is nice seeing the flags. So it can be interesting saving the Locale information in the session-scoped bean SessionData. The flags icons can be stored in the folder :

  • Deployed Resources/webapp/frontend/icon/flags


1. Updating the previous SessionData class to get Locales

Remember that the SessionData class was an example of a session scoped class that was annotated by @VaadinSessionScoped and the injected into other classes (in this case into the "LoginForm" class

Now 2 attributes are added:
  1. Locale 
  2. LangEnum
And they are initialised in the method "init()" that is annotated by @PostConstruct. Note the removing of the CURRENT_USER attribute from the session to avoid unwanted access to the application. Here is the code


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package openadmin.session;

import java.util.Locale;

import javax.annotation.PostConstruct;
//import javax.enterprise.context.SessionScoped;
//import javax.inject.Named;

import com.vaadin.cdi.annotation.VaadinSessionScoped;
import com.vaadin.flow.server.VaadinService;

import openadmin.i18n.LangEnum;
import openadmin.utils.VaadinUtils;

import lombok.Getter;
import lombok.Setter;

//@Named @SessionScoped
@VaadinSessionScoped
public class SessionData {
 
 private Locale loc=null;
 @Getter @Setter
 private LangEnum langEnum=null;
 
 
 @PostConstruct
 private void init() {
  loc=VaadinService.getCurrentRequest().getLocale();
  langEnum=LangEnum.getFromLocale(loc);
  
  System.out.println("SessionData.init() called.");
  // Remove the session attribute that informs the user, if exists
  VaadinUtils.getWrappedSession().removeAttribute("CONNECTED_USER");
 }
}

2. Adding the flag icons


There are plenty of free flag icons on the web. I have downloaded some 64 x 64 pixels flags.

Remember to place them in this folder
  • Deployed Resources/webapp/frontend/icon/flags
And create the folder route from "webapp" folder if not exists.

If not exists these folders in the webapp you should create them


3. A Combo with flags icons.

Important things to take into account:

  1. To display the flags in the combo items, we need a template that manages HTML code
  2. In this case, the combo wraps a LangEnum type (ComboBox<LangEnum>) as it is the source of the items.
  3. When the combo changes its values, it should set the session locale to the new value and it should also change all the labels in the parent form that are affected by i18n.
For changing all the labels affected by i18n in the parent form, the parent form should implement an interface (II18nChange that defines a method for changing the labels called changeI18nLabels())


Let's see the code of this interface


1
2
3
4
5
package openadmin.ui;

public interface II18nChange {
 public void changeI18nLabels();
}

Now the code of our customized combo, note the template code, the change of the session locale and how the event can change the parent form's labels.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package openadmin.ui;

import java.util.ArrayList;
import java.util.Arrays;

import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.data.renderer.TemplateRenderer;

import openadmin.i18n.LangEnum;
import openadmin.session.SessionData;

@SuppressWarnings("serial")
public class LocaleCombo extends ComboBox<LangEnum> {

 // Template to show in each choice of the combo
 private static String HMLTemplate=
  "<p> " + 
   " <img src=\"frontend/icon/flags/[[item.lang]].png\" " + 
     " width=\"40\" " + 
        " height=\"40\" " + 
     " style=\"vertical-align:middle\"" + 
   " > " + 
   " [[item.def]] " + 
  "</p>";
 
 // Constructor
 public LocaleCombo(SessionData sesData, II18nChange parentForm) {
  // Sets the item label generator that is used to produce the strings shown in the combo box for each item. By default, String.valueOf(Object) is used.
  this.setItemLabelGenerator(LangEnum::getDefinition);
  
  // Sets the data items of this component provided as a collection.
  this.setItems(new ArrayList<>(Arrays.asList(LangEnum.values())));
  
  // Sets the TemplateRenderer responsible to render the individual items in the list of possible choices of the ComboBox
  this.setRenderer(
   TemplateRenderer
    .<LangEnum>of(HMLTemplate)
    .withProperty("lang", LangEnum::name).withProperty("def", LangEnum::getDefinition));
  
  // Set the initial value of the combo
  this.setValue(sesData.getLangEnum());
  
  // Add the behaviour of the change value event that afectsd the parent form
  this.setListener(sesData, parentForm);
  
 }
 
 // Define the event for changing the value and modify all the labels affected by the i18n changes
 private void setListener(SessionData sesData, II18nChange parentForm) {
  this.addValueChangeListener(
   event -> {
    if (this.getValue()!=null) {
     
     // Update the session locale
     sesData.setLangEnum(this.getValue());
     
     // Change all labels afected by i18n changes
     parentForm.changeI18nLabels(); 
    } 
  });
 }
 
}