diff --git a/vaadin/.gitignore b/vaadin/.gitignore new file mode 100644 index 0000000000..1ef3efefbd --- /dev/null +++ b/vaadin/.gitignore @@ -0,0 +1 @@ +frontend/generated \ No newline at end of file diff --git a/vaadin/frontend/index.html b/vaadin/frontend/index.html new file mode 100644 index 0000000000..d36e593475 --- /dev/null +++ b/vaadin/frontend/index.html @@ -0,0 +1,23 @@ + + + + + + + + + + + + +
+ + diff --git a/vaadin/pom.xml b/vaadin/pom.xml index e3786471f5..ea9a3ed75a 100644 --- a/vaadin/pom.xml +++ b/vaadin/pom.xml @@ -1,13 +1,12 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 org.test vaadin 1.0-SNAPSHOT vaadin - war com.baeldung @@ -16,6 +15,11 @@ ../parent-boot-3 + + 17 + 24.3.3 + + @@ -30,39 +34,24 @@ - javax.servlet - javax.servlet-api - 4.0.1 - provided + com.vaadin + vaadin-core com.vaadin - vaadin-server - ${vaadin-server.version} - - - com.vaadin - vaadin-push - ${vaadin-push.version} - - - com.vaadin - vaadin-client-compiled - ${vaadin-client-compiled.version} - - - com.vaadin - vaadin-themes - ${vaadin-themes.version} + vaadin-spring-boot-starter org.springframework.boot spring-boot-starter-data-jpa - com.vaadin - vaadin-spring-boot-starter - ${vaadin-spring-boot-starter.version} + org.springframework.boot + spring-boot-starter-validation + + + io.projectreactor + reactor-core com.h2database @@ -77,47 +66,17 @@ - - org.apache.maven.plugins - maven-war-plugin - ${maven-war-plugin.version} - - false - - WEB-INF/classes/VAADIN/widgetsets/WEB-INF/** - - com.vaadin vaadin-maven-plugin - ${vaadin.plugin.version} - - - org.apache.maven.plugins - maven-clean-plugin - ${maven-clean-plugin.version} - - - - - src/main/webapp/VAADIN/themes - - **/styles.css - **/styles.scss.cache - - - - - - - - - org.eclipse.jetty - jetty-maven-plugin - ${jetty.plugin.version} - - 2 - + ${vaadin.version} + + + + prepare-frontend + + + org.springframework.boot @@ -126,68 +85,41 @@ - - - vaadin-addons - https://maven.vaadin.com/vaadin-addons - - - - vaadin-prerelease - - false - - - - - vaadin-prereleases - https://maven.vaadin.com/vaadin-prereleases - - - vaadin-snapshots - https://oss.sonatype.org/content/repositories/vaadin-snapshots/ - - false - - - true - - - - - - vaadin-prereleases - https://maven.vaadin.com/vaadin-prereleases - - - vaadin-snapshots - https://oss.sonatype.org/content/repositories/vaadin-snapshots/ - - false - - - true - - - + + production + + + + com.vaadin + vaadin-core + + + com.vaadin + vaadin-dev + + + + + + + + com.vaadin + vaadin-maven-plugin + ${vaadin.version} + + + + build-frontend + + compile + + + + + - - - 13.0.9 - 13.0.9 - 13.0.9 - 8.8.5 - 8.8.5 - 8.8.5 - 8.8.5 - 9.3.9.v20160517 - local - mytheme - 3.0.0 - - \ No newline at end of file diff --git a/vaadin/src/main/java/com/baeldung/Application.java b/vaadin/src/main/java/com/baeldung/Application.java index 1d3084723a..b62dd79158 100644 --- a/vaadin/src/main/java/com/baeldung/Application.java +++ b/vaadin/src/main/java/com/baeldung/Application.java @@ -1,15 +1,19 @@ package com.baeldung; +import com.baeldung.data.Employee; +import com.baeldung.data.EmployeeRepository; +import com.vaadin.flow.component.page.AppShellConfigurator; +import com.vaadin.flow.component.page.Push; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; +@Push @SpringBootApplication -public class Application { +public class Application implements AppShellConfigurator { private static final Logger log = LoggerFactory.getLogger(Application.class); diff --git a/vaadin/src/main/java/com/baeldung/EmployeeEditor.java b/vaadin/src/main/java/com/baeldung/EmployeeEditor.java deleted file mode 100644 index ee312786d1..0000000000 --- a/vaadin/src/main/java/com/baeldung/EmployeeEditor.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.baeldung; - -import com.vaadin.flow.component.Key; -import com.vaadin.flow.component.KeyNotifier; -import com.vaadin.flow.component.button.Button; -import com.vaadin.flow.component.icon.VaadinIcon; -import com.vaadin.flow.component.orderedlayout.HorizontalLayout; -import com.vaadin.flow.component.orderedlayout.VerticalLayout; -import com.vaadin.flow.component.textfield.TextField; -import com.vaadin.flow.data.binder.Binder; -import com.vaadin.flow.spring.annotation.SpringComponent; -import com.vaadin.flow.spring.annotation.UIScope; -import org.springframework.beans.factory.annotation.Autowired; - -@SpringComponent -@UIScope -public class EmployeeEditor extends VerticalLayout implements KeyNotifier { - - private final EmployeeRepository repository; - - private Employee employee; - - TextField firstName = new TextField("First name"); - TextField lastName = new TextField("Last name"); - - Button save = new Button("Save", VaadinIcon.CHECK.create()); - Button cancel = new Button("Cancel"); - Button delete = new Button("Delete", VaadinIcon.TRASH.create()); - HorizontalLayout actions = new HorizontalLayout(save, cancel, delete); - - Binder binder = new Binder<>(Employee.class); - private ChangeHandler changeHandler; - - @Autowired - public EmployeeEditor(EmployeeRepository repository) { - this.repository = repository; - - add(firstName, lastName, actions); - - binder.bindInstanceFields(this); - - setSpacing(true); - - save.getElement().getThemeList().add("primary"); - delete.getElement().getThemeList().add("error"); - - addKeyPressListener(Key.ENTER, e -> save()); - - save.addClickListener(e -> save()); - delete.addClickListener(e -> delete()); - cancel.addClickListener(e -> editEmployee(employee)); - setVisible(false); - } - - void delete() { - repository.delete(employee); - changeHandler.onChange(); - } - - void save() { - repository.save(employee); - changeHandler.onChange(); - } - - public interface ChangeHandler { - void onChange(); - } - - public final void editEmployee(Employee c) { - if (c == null) { - setVisible(false); - return; - } - final boolean persisted = c.getId() != null; - if (persisted) { - employee = repository.findById(c.getId()).get(); - } else { - employee = c; - } - - cancel.setVisible(persisted); - binder.setBean(employee); - setVisible(true); - firstName.focus(); - } - - public void setChangeHandler(ChangeHandler h) { - changeHandler = h; - } -} diff --git a/vaadin/src/main/java/com/baeldung/IndexView.java b/vaadin/src/main/java/com/baeldung/IndexView.java new file mode 100644 index 0000000000..45ab534ef6 --- /dev/null +++ b/vaadin/src/main/java/com/baeldung/IndexView.java @@ -0,0 +1,25 @@ +package com.baeldung; + +import com.baeldung.introduction.PushView; +import com.baeldung.introduction.basics.VaadinFlowBasics; +import com.baeldung.introduction.FormView; +import com.baeldung.introduction.GridView; +import com.baeldung.spring.EmployeesView; +import com.vaadin.flow.component.html.H1; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.router.RouterLink; + +@Route("") +public class IndexView extends VerticalLayout { + + public IndexView() { + add(new H1("Vaadin Flow examples")); + + add(new RouterLink("Basics", VaadinFlowBasics.class)); + add(new RouterLink("Grid", GridView.class)); + add(new RouterLink("Form", FormView.class)); + add(new RouterLink("Push", PushView.class)); + add(new RouterLink("CRUD", EmployeesView.class)); + } +} diff --git a/vaadin/src/main/java/com/baeldung/MainView.java b/vaadin/src/main/java/com/baeldung/MainView.java deleted file mode 100644 index 6d4c0aaa88..0000000000 --- a/vaadin/src/main/java/com/baeldung/MainView.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.baeldung; - -import org.springframework.util.StringUtils; - -import com.vaadin.flow.component.button.Button; -import com.vaadin.flow.component.grid.Grid; -import com.vaadin.flow.component.icon.VaadinIcon; -import com.vaadin.flow.component.orderedlayout.HorizontalLayout; -import com.vaadin.flow.component.orderedlayout.VerticalLayout; -import com.vaadin.flow.component.textfield.TextField; -import com.vaadin.flow.data.value.ValueChangeMode; -import com.vaadin.flow.router.Route; - -@Route -public class MainView extends VerticalLayout { - - private final EmployeeRepository employeeRepository; - - private final EmployeeEditor editor; - - final Grid grid; - - final TextField filter; - - private final Button addNewBtn; - - public MainView(EmployeeRepository repo, EmployeeEditor editor) { - this.employeeRepository = repo; - this.editor = editor; - this.grid = new Grid<>(Employee.class); - this.filter = new TextField(); - this.addNewBtn = new Button("New employee", VaadinIcon.PLUS.create()); - - HorizontalLayout actions = new HorizontalLayout(filter, addNewBtn); - add(actions, grid, editor); - - grid.setHeight("200px"); - grid.setColumns("id", "firstName", "lastName"); - grid.getColumnByKey("id").setWidth("50px").setFlexGrow(0); - - filter.setPlaceholder("Filter by last name"); - - filter.setValueChangeMode(ValueChangeMode.EAGER); - filter.addValueChangeListener(e -> listEmployees(e.getValue())); - - grid.asSingleSelect().addValueChangeListener(e -> { - editor.editEmployee(e.getValue()); - }); - - addNewBtn.addClickListener(e -> editor.editEmployee(new Employee("", ""))); - - editor.setChangeHandler(() -> { - editor.setVisible(false); - listEmployees(filter.getValue()); - }); - - listEmployees(null); - } - - void listEmployees(String filterText) { - if (StringUtils.isEmpty(filterText)) { - grid.setItems(employeeRepository.findAll()); - } else { - grid.setItems(employeeRepository.findByLastNameStartsWithIgnoreCase(filterText)); - } - } -} diff --git a/vaadin/src/main/java/com/baeldung/data/Contact.java b/vaadin/src/main/java/com/baeldung/data/Contact.java new file mode 100644 index 0000000000..8e534d5bb0 --- /dev/null +++ b/vaadin/src/main/java/com/baeldung/data/Contact.java @@ -0,0 +1,73 @@ +package com.baeldung.data; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; + +@Entity +public class Contact { + + @Id + @GeneratedValue + private Long id; + @NotBlank + private String name; + @Email + private String email; + @Pattern(regexp = "\\d{3}-\\d{3}-\\d{4}") + private String phone; + + public Contact() { + } + + public Contact(String name, String email, String phone) { + this.name = name; + this.email = email; + this.phone = phone; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + @Override + public String toString() { + return "Contact {" + + "id=" + id + + ", name='" + name + "'" + + ", email='" + email + "'" + + ", phone='" + phone + "'" + + " }"; + } +} diff --git a/vaadin/src/main/java/com/baeldung/data/ContactRepository.java b/vaadin/src/main/java/com/baeldung/data/ContactRepository.java new file mode 100644 index 0000000000..15c4adaaa9 --- /dev/null +++ b/vaadin/src/main/java/com/baeldung/data/ContactRepository.java @@ -0,0 +1,6 @@ +package com.baeldung.data; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ContactRepository extends JpaRepository { +} diff --git a/vaadin/src/main/java/com/baeldung/data/DataInitializer.java b/vaadin/src/main/java/com/baeldung/data/DataInitializer.java new file mode 100644 index 0000000000..dba162d4e4 --- /dev/null +++ b/vaadin/src/main/java/com/baeldung/data/DataInitializer.java @@ -0,0 +1,46 @@ +package com.baeldung.data; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +@Component +public class DataInitializer implements CommandLineRunner { + + private final ContactRepository contactRepository; + + public DataInitializer(ContactRepository contactRepository) { + this.contactRepository = contactRepository; + } + + private final List firstNames = List.of( + "John", "Jane", "Emily", "Michael", "Sarah", "James", "Mary", "Robert", + "Patricia", "William", "Linda", "David", "Barbara", "Richard", "Susan", + "Joseph", "Jessica", "Thomas", "Karen", "Charles" + ); + private final List lastNames = List.of( + "Doe", "Smith", "Johnson", "Brown", "Davis", "Miller", "Wilson", "Moore", + "Taylor", "Anderson", "Thomas", "Jackson", "White", "Harris", "Martin", + "Thompson", "Garcia", "Martinez", "Robinson", "Clark" + ); + + @Override + public void run(String... args) throws Exception { + var contacts = new ArrayList(); + Random random = new Random(); + + for (int i = 0; i < 100; i++) { + String firstName = firstNames.get(random.nextInt(firstNames.size())); + String lastName = lastNames.get(random.nextInt(lastNames.size())); + String email = String.format("%s.%s@example.com", firstName.toLowerCase(), lastName.toLowerCase()); + String phone = String.format("555-%03d-%04d", random.nextInt(1000), random.nextInt(10000)); + + contacts.add(new Contact(firstName + " " + lastName, email, phone)); + } + + contactRepository.saveAll(contacts); + } +} diff --git a/vaadin/src/main/java/com/baeldung/Employee.java b/vaadin/src/main/java/com/baeldung/data/Employee.java similarity index 75% rename from vaadin/src/main/java/com/baeldung/Employee.java rename to vaadin/src/main/java/com/baeldung/data/Employee.java index 75a0dc84b3..cdaa426eeb 100644 --- a/vaadin/src/main/java/com/baeldung/Employee.java +++ b/vaadin/src/main/java/com/baeldung/data/Employee.java @@ -1,8 +1,10 @@ -package com.baeldung; +package com.baeldung.data; + import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; +import jakarta.validation.constraints.Size; @Entity public class Employee { @@ -11,11 +13,13 @@ public class Employee { @GeneratedValue private Long id; + @Size(min = 2, message = "First name must have at least 2 characters") private String firstName; + @Size(min = 2, message = "Last name must have at least 2 characters") private String lastName; - protected Employee() { + public Employee() { } public Employee(String firstName, String lastName) { @@ -27,6 +31,10 @@ public class Employee { return id; } + public void setId(Long id) { + this.id = id; + } + public String getFirstName() { return firstName; } diff --git a/vaadin/src/main/java/com/baeldung/EmployeeRepository.java b/vaadin/src/main/java/com/baeldung/data/EmployeeRepository.java similarity index 89% rename from vaadin/src/main/java/com/baeldung/EmployeeRepository.java rename to vaadin/src/main/java/com/baeldung/data/EmployeeRepository.java index 044160da78..0d54148c85 100644 --- a/vaadin/src/main/java/com/baeldung/EmployeeRepository.java +++ b/vaadin/src/main/java/com/baeldung/data/EmployeeRepository.java @@ -1,4 +1,4 @@ -package com.baeldung; +package com.baeldung.data; import java.util.List; diff --git a/vaadin/src/main/java/com/baeldung/introduction/BindData.java b/vaadin/src/main/java/com/baeldung/introduction/BindData.java deleted file mode 100644 index 299554c039..0000000000 --- a/vaadin/src/main/java/com/baeldung/introduction/BindData.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.baeldung.introduction; - -public class BindData { - - private String bindName; - - public BindData(String bindName){ - this.bindName = bindName; - } - - public String getBindName() { - return bindName; - } - - public void setBindName(String bindName) { - this.bindName = bindName; - } - - -} diff --git a/vaadin/src/main/java/com/baeldung/introduction/FormView.java b/vaadin/src/main/java/com/baeldung/introduction/FormView.java new file mode 100644 index 0000000000..34bc334b27 --- /dev/null +++ b/vaadin/src/main/java/com/baeldung/introduction/FormView.java @@ -0,0 +1,88 @@ +package com.baeldung.introduction; + +import com.baeldung.data.Contact; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.html.H2; +import com.vaadin.flow.component.notification.Notification; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.binder.BeanValidationBinder; +import com.vaadin.flow.data.binder.Binder; +import com.vaadin.flow.router.Route; + +@Route("form") +public class FormView extends HorizontalLayout { + + public FormView() { + addBeanValidationExample(); + addCustomValidationExample(); + } + + private void addBeanValidationExample() { + var nameField = new TextField("Name"); + var emailField = new TextField("Email"); + var phoneField = new TextField("Phone"); + var saveButton = new Button("Save"); + + var binder = new BeanValidationBinder<>(Contact.class); + + binder.forField(nameField).bind(Contact::getName, Contact::setName); + binder.forField(emailField).bind(Contact::getEmail, Contact::setEmail); + binder.forField(phoneField).bind(Contact::getPhone, Contact::setPhone); + + var contact = new Contact("John Doe", "john@doe.com", "123-456-7890"); + binder.setBean(contact); + + saveButton.addClickListener(e -> { + if (binder.validate().isOk()) { + Notification.show("Saved " + contact); + } + }); + + add(new VerticalLayout( + new H2("Bean Validation Example"), + nameField, + emailField, + phoneField, + saveButton + )); + } + + private void addCustomValidationExample() { + + var nameField = new TextField("Name"); + var emailField = new TextField("Email"); + var phoneField = new TextField("Phone"); + var saveButton = new Button("Save"); + + var binder = new Binder<>(Contact.class); + + binder.forField(nameField) + .asRequired() + .bind(Contact::getName, Contact::setName); + binder.forField(emailField) + .withValidator(email -> email.contains("@"), "Not a valid email address") + .bind(Contact::getEmail, Contact::setEmail); + binder.forField(phoneField) + .withValidator(phone -> phone.matches("\\d{3}-\\d{3}-\\d{4}"), "Not a valid phone number") + .bind(Contact::getPhone, Contact::setPhone); + + var contact = new Contact("John Doe", "john@doe.com", "123-456-7890"); + binder.setBean(contact); + + saveButton.addClickListener(e -> { + if (binder.validate().isOk()) { + Notification.show("Saved " + contact); + } + }); + + add(new VerticalLayout( + new H2("Custom Validation Example"), + nameField, + emailField, + phoneField, + saveButton + )); + } +} diff --git a/vaadin/src/main/java/com/baeldung/introduction/GridView.java b/vaadin/src/main/java/com/baeldung/introduction/GridView.java new file mode 100644 index 0000000000..3d9500d7d8 --- /dev/null +++ b/vaadin/src/main/java/com/baeldung/introduction/GridView.java @@ -0,0 +1,40 @@ +package com.baeldung.introduction; + +import com.baeldung.data.Contact; +import com.baeldung.data.ContactRepository; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.html.H1; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.spring.data.VaadinSpringDataHelpers; + +@Route("grid") +public class GridView extends VerticalLayout { + + public GridView(ContactRepository contactRepository) { + + add(new H1("Using data grids")); + + // Define a grid for a Contact entity + var grid = new Grid(); + grid.addColumn(Contact::getName).setHeader("Name"); + grid.addColumn(Contact::getEmail).setHeader("Email"); + grid.addColumn(Contact::getPhone).setHeader("Phone"); + + // There are two ways to populate the grid with data: + + // 1) In-memory with a list of items + var contacts = contactRepository.findAll(); + grid.setItems(contacts); + + // 2) with a callback to lazily load items from a data source. + grid.setItems(query -> { + // Turn the page, offset, filter, sort and other info in query into a Spring Data PageRequest + var pageRequest = VaadinSpringDataHelpers.toSpringPageRequest(query); + return contactRepository.findAll(pageRequest).stream(); + }); + + add(grid); + + } +} diff --git a/vaadin/src/main/java/com/baeldung/introduction/PushView.java b/vaadin/src/main/java/com/baeldung/introduction/PushView.java new file mode 100644 index 0000000000..b49aa06e4d --- /dev/null +++ b/vaadin/src/main/java/com/baeldung/introduction/PushView.java @@ -0,0 +1,32 @@ +package com.baeldung.introduction; + +import com.vaadin.flow.component.html.Span; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.Route; +import reactor.core.publisher.Flux; + +import java.time.Duration; +import java.time.Instant; + +// Ensure you have @Push annotation in your Application class +@Route("push") +public class PushView extends VerticalLayout { + + public PushView() { + var output = new Span(); + + // Publish server time once a second + var serverTime = Flux.interval(Duration.ofSeconds(1)) + .map(o -> "Server time: " + Instant.now()); + + + serverTime.subscribe(time -> + // ui.access is required to update the UI from a background thread + getUI().ifPresent(ui -> + ui.access(() -> output.setText(time)) + ) + ); + + add(output); + } +} diff --git a/vaadin/src/main/java/com/baeldung/introduction/Row.java b/vaadin/src/main/java/com/baeldung/introduction/Row.java deleted file mode 100644 index 412a286376..0000000000 --- a/vaadin/src/main/java/com/baeldung/introduction/Row.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.baeldung.introduction; - -public class Row { - - private String column1; - - private String column2; - - private String column3; - - public Row() { - - } - - public Row(String column1, String column2, String column3) { - super(); - this.column1 = column1; - this.column2 = column2; - this.column3 = column3; - } - - public String getColumn1() { - return column1; - } - - public void setColumn1(String column1) { - this.column1 = column1; - } - - public String getColumn2() { - return column2; - } - - public void setColumn2(String column2) { - this.column2 = column2; - } - - public String getColumn3() { - return column3; - } - - public void setColumn3(String column3) { - this.column3 = column3; - } -} \ No newline at end of file diff --git a/vaadin/src/main/java/com/baeldung/introduction/VaadinUI.java b/vaadin/src/main/java/com/baeldung/introduction/VaadinUI.java deleted file mode 100644 index 05a8340bde..0000000000 --- a/vaadin/src/main/java/com/baeldung/introduction/VaadinUI.java +++ /dev/null @@ -1,281 +0,0 @@ -package com.baeldung.introduction; - -import java.time.Instant; -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -import com.vaadin.annotations.Push; -import com.vaadin.annotations.Theme; -import com.vaadin.annotations.VaadinServletConfiguration; -import com.vaadin.data.Binder; -import com.vaadin.data.validator.StringLengthValidator; -import com.vaadin.icons.VaadinIcons; -import com.vaadin.server.ExternalResource; -import com.vaadin.server.VaadinRequest; -import com.vaadin.server.VaadinServlet; -import com.vaadin.ui.Button; -import com.vaadin.ui.CheckBox; -import com.vaadin.ui.ComboBox; -import com.vaadin.ui.DateField; -import com.vaadin.ui.FormLayout; -import com.vaadin.ui.Grid; -import com.vaadin.ui.GridLayout; -import com.vaadin.ui.HorizontalLayout; -import com.vaadin.ui.InlineDateField; -import com.vaadin.ui.Label; -import com.vaadin.ui.Link; -import com.vaadin.ui.ListSelect; -import com.vaadin.ui.NativeButton; -import com.vaadin.ui.NativeSelect; -import com.vaadin.ui.Panel; -import com.vaadin.ui.PasswordField; -import com.vaadin.ui.RichTextArea; -import com.vaadin.ui.TextArea; -import com.vaadin.ui.TextField; -import com.vaadin.ui.TwinColSelect; -import com.vaadin.ui.UI; -import com.vaadin.ui.VerticalLayout; -import jakarta.servlet.annotation.WebServlet; - -@SuppressWarnings("serial") -@Push -@Theme("mytheme") -public class VaadinUI extends UI { - - private Label currentTime; - - @SuppressWarnings({ "rawtypes", "unchecked" }) - @Override - protected void init(VaadinRequest vaadinRequest) { - final VerticalLayout verticalLayout = new VerticalLayout(); - verticalLayout.setSpacing(true); - verticalLayout.setMargin(true); - final GridLayout gridLayout = new GridLayout(3, 2); - gridLayout.setSpacing(true); - gridLayout.setMargin(true); - final HorizontalLayout horizontalLayout = new HorizontalLayout(); - horizontalLayout.setSpacing(true); - horizontalLayout.setMargin(true); - final FormLayout formLayout = new FormLayout(); - formLayout.setSpacing(true); - formLayout.setMargin(true); - final GridLayout buttonLayout = new GridLayout(3, 5); - buttonLayout.setMargin(true); - buttonLayout.setSpacing(true); - - final Label label = new Label(); - label.setId("Label"); - label.setValue("Label Value"); - label.setCaption("Label"); - gridLayout.addComponent(label); - - final Link link = new Link("Baeldung", new ExternalResource("http://www.baeldung.com/")); - link.setId("Link"); - link.setTargetName("_blank"); - gridLayout.addComponent(link); - - final TextField textField = new TextField(); - textField.setId("TextField"); - textField.setCaption("TextField:"); - textField.setValue("TextField Value"); - textField.setIcon(VaadinIcons.USER); - gridLayout.addComponent(textField); - - final TextArea textArea = new TextArea(); - textArea.setCaption("TextArea"); - textArea.setId("TextArea"); - textArea.setValue("TextArea Value"); - gridLayout.addComponent(textArea); - - final DateField dateField = new DateField("DateField", LocalDate.ofEpochDay(0)); - dateField.setId("DateField"); - gridLayout.addComponent(dateField); - - final PasswordField passwordField = new PasswordField(); - passwordField.setId("PasswordField"); - passwordField.setCaption("PasswordField:"); - passwordField.setValue("password"); - gridLayout.addComponent(passwordField); - - final RichTextArea richTextArea = new RichTextArea(); - richTextArea.setCaption("Rich Text Area"); - richTextArea.setValue("

RichTextArea

"); - richTextArea.setSizeFull(); - - Panel richTextPanel = new Panel(); - richTextPanel.setContent(richTextArea); - - final InlineDateField inlineDateField = new InlineDateField(); - inlineDateField.setValue(LocalDate.ofEpochDay(0)); - inlineDateField.setCaption("Inline Date Field"); - horizontalLayout.addComponent(inlineDateField); - - Button normalButton = new Button("Normal Button"); - normalButton.setId("NormalButton"); - normalButton.addClickListener(e -> { - label.setValue("CLICK"); - }); - buttonLayout.addComponent(normalButton); - - Button tinyButton = new Button("Tiny Button"); - tinyButton.addStyleName("tiny"); - buttonLayout.addComponent(tinyButton); - - Button smallButton = new Button("Small Button"); - smallButton.addStyleName("small"); - buttonLayout.addComponent(smallButton); - - Button largeButton = new Button("Large Button"); - largeButton.addStyleName("large"); - buttonLayout.addComponent(largeButton); - - Button hugeButton = new Button("Huge Button"); - hugeButton.addStyleName("huge"); - buttonLayout.addComponent(hugeButton); - - Button disabledButton = new Button("Disabled Button"); - disabledButton.setDescription("This button cannot be clicked"); - disabledButton.setEnabled(false); - buttonLayout.addComponent(disabledButton); - - Button dangerButton = new Button("Danger Button"); - dangerButton.addStyleName("danger"); - buttonLayout.addComponent(dangerButton); - - Button friendlyButton = new Button("Friendly Button"); - friendlyButton.addStyleName("friendly"); - buttonLayout.addComponent(friendlyButton); - - Button primaryButton = new Button("Primary Button"); - primaryButton.addStyleName("primary"); - buttonLayout.addComponent(primaryButton); - - NativeButton nativeButton = new NativeButton("Native Button"); - buttonLayout.addComponent(nativeButton); - - Button iconButton = new Button("Icon Button"); - iconButton.setIcon(VaadinIcons.ALIGN_LEFT); - buttonLayout.addComponent(iconButton); - - Button borderlessButton = new Button("BorderLess Button"); - borderlessButton.addStyleName("borderless"); - buttonLayout.addComponent(borderlessButton); - - Button linkButton = new Button("Link Button"); - linkButton.addStyleName("link"); - buttonLayout.addComponent(linkButton); - - Button quietButton = new Button("Quiet Button"); - quietButton.addStyleName("quiet"); - buttonLayout.addComponent(quietButton); - - horizontalLayout.addComponent(buttonLayout); - - final CheckBox checkbox = new CheckBox("CheckBox"); - checkbox.setValue(true); - checkbox.addValueChangeListener(e -> checkbox.setValue(!checkbox.getValue())); - formLayout.addComponent(checkbox); - - List numbers = new ArrayList(); - numbers.add("One"); - numbers.add("Ten"); - numbers.add("Eleven"); - ComboBox comboBox = new ComboBox("ComboBox"); - comboBox.setItems(numbers); - formLayout.addComponent(comboBox); - - ListSelect listSelect = new ListSelect("ListSelect"); - listSelect.setItems(numbers); - listSelect.setRows(2); - formLayout.addComponent(listSelect); - - NativeSelect nativeSelect = new NativeSelect("NativeSelect"); - nativeSelect.setItems(numbers); - formLayout.addComponent(nativeSelect); - - TwinColSelect twinColSelect = new TwinColSelect("TwinColSelect"); - twinColSelect.setItems(numbers); - - Grid grid = new Grid(Row.class); - grid.setColumns("column1", "column2", "column3"); - Row row1 = new Row("Item1", "Item2", "Item3"); - Row row2 = new Row("Item4", "Item5", "Item6"); - List rows = new ArrayList(); - rows.add(row1); - rows.add(row2); - grid.setItems(rows); - - Panel panel = new Panel("Panel"); - panel.setContent(grid); - panel.setSizeUndefined(); - - Panel serverPushPanel = new Panel("Server Push"); - FormLayout timeLayout = new FormLayout(); - timeLayout.setSpacing(true); - timeLayout.setMargin(true); - currentTime = new Label("No TIME..."); - timeLayout.addComponent(currentTime); - serverPushPanel.setContent(timeLayout); - serverPushPanel.setSizeUndefined(); - ScheduledExecutorService scheduleExecutor = Executors.newScheduledThreadPool(1); - Runnable task = () -> { - currentTime.setValue("Current Time : " + Instant.now()); - }; - scheduleExecutor.scheduleWithFixedDelay(task, 0, 1, TimeUnit.SECONDS); - - FormLayout dataBindingLayout = new FormLayout(); - dataBindingLayout.setSpacing(true); - dataBindingLayout.setMargin(true); - - Binder binder = new Binder<>(); - BindData bindData = new BindData("BindData"); - binder.readBean(bindData); - TextField bindedTextField = new TextField(); - bindedTextField.setWidth("250px"); - binder.forField(bindedTextField).bind(BindData::getBindName, BindData::setBindName); - dataBindingLayout.addComponent(bindedTextField); - - FormLayout validatorLayout = new FormLayout(); - validatorLayout.setSpacing(true); - validatorLayout.setMargin(true); - - HorizontalLayout textValidatorLayout = new HorizontalLayout(); - textValidatorLayout.setSpacing(true); - textValidatorLayout.setMargin(true); - - - BindData stringValidatorBindData = new BindData(""); - TextField stringValidator = new TextField(); - Binder stringValidatorBinder = new Binder<>(); - stringValidatorBinder.setBean(stringValidatorBindData); - stringValidatorBinder.forField(stringValidator) - .withValidator(new StringLengthValidator("String must have 2-5 characters lenght", 2, 5)) - .bind(BindData::getBindName, BindData::setBindName); - - textValidatorLayout.addComponent(stringValidator); - Button buttonStringValidator = new Button("Validate String"); - buttonStringValidator.addClickListener(e -> stringValidatorBinder.validate()); - textValidatorLayout.addComponent(buttonStringValidator); - - validatorLayout.addComponent(textValidatorLayout); - verticalLayout.addComponent(gridLayout); - verticalLayout.addComponent(richTextPanel); - verticalLayout.addComponent(horizontalLayout); - verticalLayout.addComponent(formLayout); - verticalLayout.addComponent(twinColSelect); - verticalLayout.addComponent(panel); - verticalLayout.addComponent(serverPushPanel); - verticalLayout.addComponent(dataBindingLayout); - verticalLayout.addComponent(validatorLayout); - setContent(verticalLayout); - } - - @WebServlet(urlPatterns = "/VAADIN/*", name = "MyUIServlet", asyncSupported = true) - @VaadinServletConfiguration(ui = VaadinUI.class, productionMode = false) - public static class MyUIServlet extends VaadinServlet { - } -} \ No newline at end of file diff --git a/vaadin/src/main/java/com/baeldung/introduction/basics/ExampleLayout.java b/vaadin/src/main/java/com/baeldung/introduction/basics/ExampleLayout.java new file mode 100644 index 0000000000..6d6e2b2b7d --- /dev/null +++ b/vaadin/src/main/java/com/baeldung/introduction/basics/ExampleLayout.java @@ -0,0 +1,45 @@ +package com.baeldung.introduction.basics; + +import com.baeldung.data.Contact; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.splitlayout.SplitLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.router.Route; + +import java.util.List; + +@Route("example-layout") +public class ExampleLayout extends SplitLayout { + + public ExampleLayout() { + var grid = new Grid<>(Contact.class); + grid.setColumns("name", "email", "phone"); + grid.setItems(List.of( + new Contact("John Doe", "john@doe.com", "123 456 789"), + new Contact("Jane Doe", "jane@doe.com", "987 654 321") + )); + + var form = new VerticalLayout(); + + var nameField = new TextField("Name"); + var emailField = new TextField("Email"); + var phoneField = new TextField("Phone"); + var saveButton = new Button("Save"); + var cancelButton = new Button("Cancel"); + + form.add( + nameField, + emailField, + phoneField, + new HorizontalLayout(saveButton, cancelButton) + ); + + setSizeFull(); + setSplitterPosition(70); + addToPrimary(grid); + addToSecondary(form); + } +} diff --git a/vaadin/src/main/java/com/baeldung/introduction/basics/HelloWorldView.java b/vaadin/src/main/java/com/baeldung/introduction/basics/HelloWorldView.java new file mode 100644 index 0000000000..477cf378c6 --- /dev/null +++ b/vaadin/src/main/java/com/baeldung/introduction/basics/HelloWorldView.java @@ -0,0 +1,13 @@ +package com.baeldung.introduction.basics; + +import com.vaadin.flow.component.html.H1; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.Route; + +@Route("hello-world") +public class HelloWorldView extends VerticalLayout { + + public HelloWorldView() { + add(new H1("Hello, World!")); + } +} diff --git a/vaadin/src/main/java/com/baeldung/introduction/basics/VaadinFlowBasics.java b/vaadin/src/main/java/com/baeldung/introduction/basics/VaadinFlowBasics.java new file mode 100644 index 0000000000..7a22eea0ab --- /dev/null +++ b/vaadin/src/main/java/com/baeldung/introduction/basics/VaadinFlowBasics.java @@ -0,0 +1,63 @@ +package com.baeldung.introduction.basics; + +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.checkbox.Checkbox; +import com.vaadin.flow.component.html.H1; +import com.vaadin.flow.component.notification.Notification; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.router.RouterLink; + +// The @Route annotation defines the URL path for the view +// Any component, most commonly a layout, can be used as a view +@Route("basics") +public class VaadinFlowBasics extends VerticalLayout { + public VaadinFlowBasics() { + + // Add components to the layout with the add method + add(new H1("Vaadin Flow Basics")); + + // Components are Java objects + var textField = new TextField("Name"); + var button = new Button("Click me"); + + // Layouts define the structure of the UI + var verticalLayout = new VerticalLayout( + new Button("Top"), + new Button("Middle"), + new Button("Bottom") + ); + add(verticalLayout); + + var horizontalLayout = new HorizontalLayout( + new Button("Left"), + new Button("Center"), + new Button("Right") + ); + add(horizontalLayout); + + // Layouts can be nested for more complex structures + var nestedLayout = new VerticalLayout( + new HorizontalLayout(new Button("Top Left"), new Button("Top Right")), + new HorizontalLayout(new Button("Bottom Left"), new Button("Bottom Right")) + ); + add(nestedLayout); + + add(new RouterLink("Example layout", ExampleLayout.class)); + + // Use RouterLink to navigate to other views + var link = new RouterLink("Hello world view", HelloWorldView.class); + add(link); + + // Use events to react to user input + var nameField = new TextField("Your name"); + var helloButton = new Button("Say hello"); + helloButton.addClickListener(e -> { + Notification.show("Hello, " + nameField.getValue()); + }); + add(nameField, helloButton); + + } +} diff --git a/vaadin/src/main/java/com/baeldung/spring/EmployeeEditor.java b/vaadin/src/main/java/com/baeldung/spring/EmployeeEditor.java new file mode 100644 index 0000000000..7984ecce05 --- /dev/null +++ b/vaadin/src/main/java/com/baeldung/spring/EmployeeEditor.java @@ -0,0 +1,86 @@ +package com.baeldung.spring; + +import com.baeldung.data.Employee; +import com.vaadin.flow.component.Composite; +import com.vaadin.flow.component.Key; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.icon.VaadinIcon; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.binder.BeanValidationBinder; +import com.vaadin.flow.data.binder.Binder; + +public class EmployeeEditor extends Composite { + + public interface SaveListener { + void onSave(Employee employee); + } + + public interface DeleteListener { + void onDelete(Employee employee); + } + + public interface CancelListener { + void onCancel(); + } + + private Employee employee; + + private SaveListener saveListener; + private DeleteListener deleteListener; + private CancelListener cancelListener; + + private final Binder binder = new BeanValidationBinder<>(Employee.class); + +public EmployeeEditor() { + var firstName = new TextField("First name"); + var lastName = new TextField("Last name"); + + var save = new Button("Save", VaadinIcon.CHECK.create()); + var cancel = new Button("Cancel"); + var delete = new Button("Delete", VaadinIcon.TRASH.create()); + + binder.forField(firstName).bind("firstName"); + binder.forField(lastName).bind("lastName"); + + save.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + save.addClickListener(e -> save()); + save.addClickShortcut(Key.ENTER); + + delete.addThemeVariants(ButtonVariant.LUMO_ERROR); + delete.addClickListener(e -> deleteListener.onDelete(employee)); + + cancel.addClickListener(e -> cancelListener.onCancel()); + + getContent().add(firstName, lastName, new HorizontalLayout(save, cancel, delete)); +} + + private void save() { + // Save the form into a new instance of Employee + var updated = new Employee(); + updated.setId(employee.getId()); + + if (binder.writeBeanIfValid(updated)) { + saveListener.onSave(updated); + } + } + + public void setEmployee(Employee employee) { + this.employee = employee; + binder.readBean(employee); + } + + public void setSaveListener(SaveListener saveListener) { + this.saveListener = saveListener; + } + + public void setDeleteListener(DeleteListener deleteListener) { + this.deleteListener = deleteListener; + } + + public void setCancelListener(CancelListener cancelListener) { + this.cancelListener = cancelListener; + } +} diff --git a/vaadin/src/main/java/com/baeldung/spring/EmployeesView.java b/vaadin/src/main/java/com/baeldung/spring/EmployeesView.java new file mode 100644 index 0000000000..edb1f7a75b --- /dev/null +++ b/vaadin/src/main/java/com/baeldung/spring/EmployeesView.java @@ -0,0 +1,95 @@ +package com.baeldung.spring; + +import com.baeldung.data.Employee; +import com.baeldung.data.EmployeeRepository; + +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.icon.VaadinIcon; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.value.ValueChangeMode; +import com.vaadin.flow.router.Route; + +@Route("employees") +public class EmployeesView extends VerticalLayout { + + private final EmployeeRepository employeeRepository; + + private final TextField filter; + private final Grid grid; + private final EmployeeEditor editor; + + + public EmployeesView(EmployeeRepository repo) { + employeeRepository = repo; + + // Create components + var addButton = new Button("New employee", VaadinIcon.PLUS.create()); + filter = new TextField(); + grid = new Grid<>(Employee.class); + editor = new EmployeeEditor(); + + // Configure components + configureEditor(); + + addButton.addClickListener(e -> editEmployee(new Employee())); + + filter.setPlaceholder("Filter by last name"); + filter.setValueChangeMode(ValueChangeMode.LAZY); + filter.addValueChangeListener(e -> updateEmployees(e.getValue())); + + grid.setHeight("200px"); + grid.asSingleSelect().addValueChangeListener(e -> editEmployee(e.getValue())); + + // Compose layout + var actionsLayout = new HorizontalLayout(filter, addButton); + add(actionsLayout, grid, editor); + + // List customers + updateEmployees(""); + } + + private void configureEditor() { + editor.setVisible(false); + + editor.setSaveListener(employee -> { + var saved = employeeRepository.save(employee); + updateEmployees(filter.getValue()); + editor.setEmployee(null); + grid.asSingleSelect().setValue(saved); + }); + + editor.setDeleteListener(employee -> { + employeeRepository.delete(employee); + updateEmployees(filter.getValue()); + editEmployee(null); + }); + + editor.setCancelListener(() -> { + editEmployee(null); + }); + } + + private void editEmployee(Employee employee) { + editor.setEmployee(employee); + + if (employee != null) { + editor.setVisible(true); + } else { + // Deselect grid + grid.asSingleSelect().setValue(null); + editor.setVisible(false); + } + + } + + private void updateEmployees(String filterText) { + if (filterText.isEmpty()) { + grid.setItems(employeeRepository.findAll()); + } else { + grid.setItems(employeeRepository.findByLastNameStartsWithIgnoreCase(filterText)); + } + } +} diff --git a/vaadin/src/main/webapp/VAADIN/themes/mytheme/addons.scss b/vaadin/src/main/webapp/VAADIN/themes/mytheme/addons.scss deleted file mode 100644 index a5670b70c7..0000000000 --- a/vaadin/src/main/webapp/VAADIN/themes/mytheme/addons.scss +++ /dev/null @@ -1,7 +0,0 @@ -/* This file is automatically managed and will be overwritten from time to time. */ -/* Do not manually edit this file. */ - -/* Import and include this mixin into your project theme to include the addon themes */ -@mixin addons { -} - diff --git a/vaadin/src/main/webapp/VAADIN/themes/mytheme/favicon.ico b/vaadin/src/main/webapp/VAADIN/themes/mytheme/favicon.ico deleted file mode 100644 index ffb34a65c7..0000000000 Binary files a/vaadin/src/main/webapp/VAADIN/themes/mytheme/favicon.ico and /dev/null differ diff --git a/vaadin/src/main/webapp/VAADIN/themes/mytheme/mytheme.scss b/vaadin/src/main/webapp/VAADIN/themes/mytheme/mytheme.scss deleted file mode 100644 index 2c5fb8b944..0000000000 --- a/vaadin/src/main/webapp/VAADIN/themes/mytheme/mytheme.scss +++ /dev/null @@ -1,38 +0,0 @@ -// If you edit this file you need to compile the theme. See README.md for details. - -// Global variable overrides. Must be declared before importing Valo. - -// Defines the plaintext font size, weight and family. Font size affects general component sizing. -//$v-font-size: 16px; -//$v-font-weight: 300; -//$v-font-family: "Open Sans", sans-serif; - -// Defines the border used by all components. -//$v-border: 1px solid (v-shade 0.7); -//$v-border-radius: 4px; - -// Affects the color of some component elements, e.g Button, Panel title, etc -//$v-background-color: hsl(210, 0%, 98%); -// Affects the color of content areas, e.g Panel and Window content, TextField input etc -//$v-app-background-color: $v-background-color; - -// Affects the visual appearance of all components -//$v-gradient: v-linear 8%; -//$v-bevel-depth: 30%; -//$v-shadow-opacity: 5%; - -// Defines colors for indicating status (focus, success, failure) -//$v-focus-color: valo-focus-color(); // Calculates a suitable color automatically -//$v-friendly-color: #2c9720; -//$v-error-indicator-color: #ed473b; - -// For more information, see: https://vaadin.com/book/-/page/themes.valo.html -// Example variants can be copy/pasted from https://vaadin.com/wiki/-/wiki/Main/Valo+Examples - -@import "../valo/valo.scss"; - -@mixin mytheme { - @include valo; - - // Insert your own theme rules here -} diff --git a/vaadin/src/main/webapp/VAADIN/themes/mytheme/styles.scss b/vaadin/src/main/webapp/VAADIN/themes/mytheme/styles.scss deleted file mode 100644 index bba1d493c0..0000000000 --- a/vaadin/src/main/webapp/VAADIN/themes/mytheme/styles.scss +++ /dev/null @@ -1,11 +0,0 @@ -@import "mytheme.scss"; -@import "addons.scss"; - -// This file prefixes all rules with the theme name to avoid causing conflicts with other themes. -// The actual styles should be defined in mytheme.scss - -.mytheme { - @include addons; - @include mytheme; - -}