Demo application to show the contrast problem with warning messages using the Lumo theme of Vaadin.
Signed-off-by: Marcus Fihlon <marcus@fihlon.swiss>
This commit is contained in:
parent
6317f9e12f
commit
6679eada69
16 changed files with 105 additions and 458 deletions
87
README.md
87
README.md
|
@ -1,86 +1 @@
|
||||||
# App README
|
Demo application to show the contrast problem with warning messages using the Lumo theme of Vaadin.
|
||||||
|
|
||||||
- [ ] TODO Replace or update this README with instructions relevant to your application
|
|
||||||
|
|
||||||
## Project Structure
|
|
||||||
|
|
||||||
The sources of your App have the following structure:
|
|
||||||
|
|
||||||
```
|
|
||||||
src
|
|
||||||
├── main/frontend
|
|
||||||
│ └── themes
|
|
||||||
│ └── default
|
|
||||||
│ ├── styles.css
|
|
||||||
│ └── theme.json
|
|
||||||
├── main/java
|
|
||||||
│ └── [application package]
|
|
||||||
│ ├── base
|
|
||||||
│ │ └── ui
|
|
||||||
│ │ ├── component
|
|
||||||
│ │ │ └── ViewToolbar.java
|
|
||||||
│ │ ├── MainErrorHandler.java
|
|
||||||
│ │ └── MainLayout.java
|
|
||||||
│ ├── examplefeature
|
|
||||||
│ │ ├── ui
|
|
||||||
│ │ │ └── TaskListView.java
|
|
||||||
│ │ ├── Task.java
|
|
||||||
│ │ ├── TaskRepository.java
|
|
||||||
│ │ └── TaskService.java
|
|
||||||
│ └── Application.java
|
|
||||||
└── test/java
|
|
||||||
└── [application package]
|
|
||||||
└── examplefeature
|
|
||||||
└── TaskServiceTest.java
|
|
||||||
```
|
|
||||||
|
|
||||||
The main entry point into the application is `Application.java`. This class contains the `main()` method that start up
|
|
||||||
the Spring Boot application.
|
|
||||||
|
|
||||||
The skeleton follows a *feature-based package structure*, organizing code by *functional units* rather than traditional
|
|
||||||
architectural layers. It includes two feature packages: `base` and `examplefeature`.
|
|
||||||
|
|
||||||
* The `base` package contains classes meant for reuse across different features, either through composition or
|
|
||||||
inheritance. You can use them as-is, tweak them to your needs, or remove them.
|
|
||||||
* The `examplefeature` package is an example feature package that demonstrates the structure. It represents a
|
|
||||||
*self-contained unit of functionality*, including UI components, business logic, data access, and an integration test.
|
|
||||||
Once you create your own features, *you'll remove this package*.
|
|
||||||
|
|
||||||
The `src/main/frontend` directory contains an empty theme called `default`, based on the Lumo theme. It is activated in
|
|
||||||
the `Application` class, using the `@Theme` annotation.
|
|
||||||
|
|
||||||
## Starting in Development Mode
|
|
||||||
|
|
||||||
To start the application in development mode, import it into your IDE and run the `Application` class.
|
|
||||||
You can also start the application from the command line by running:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./mvnw
|
|
||||||
```
|
|
||||||
|
|
||||||
## Building for Production
|
|
||||||
|
|
||||||
To build the application in production mode, run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./mvnw -Pproduction package
|
|
||||||
```
|
|
||||||
|
|
||||||
To build a Docker image, run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker build -t my-application:latest .
|
|
||||||
```
|
|
||||||
|
|
||||||
If you use commercial components, pass the license key as a build secret:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker build --secret id=proKey,src=$HOME/.vaadin/proKey .
|
|
||||||
```
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
The [Getting Started](https://vaadin.com/docs/latest/getting-started) guide will quickly familiarize you with your new
|
|
||||||
App implementation. You'll learn how to set up your development environment, understand the project
|
|
||||||
structure, and find resources to help you add muscles to your skeleton — transforming it into a fully-featured
|
|
||||||
application.
|
|
||||||
|
|
0
mvnw
vendored
Executable file → Normal file
0
mvnw
vendored
Executable file → Normal file
32
src/main/bundles/README.md
Normal file
32
src/main/bundles/README.md
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
This directory is automatically generated by Vaadin and contains the pre-compiled
|
||||||
|
frontend files/resources for your project (frontend development bundle).
|
||||||
|
|
||||||
|
It should be added to Version Control System and committed, so that other developers
|
||||||
|
do not have to compile it again.
|
||||||
|
|
||||||
|
Frontend development bundle is automatically updated when needed:
|
||||||
|
- an npm/pnpm package is added with @NpmPackage or directly into package.json
|
||||||
|
- CSS, JavaScript or TypeScript files are added with @CssImport, @JsModule or @JavaScript
|
||||||
|
- Vaadin add-on with front-end customizations is added
|
||||||
|
- Custom theme imports/assets added into 'theme.json' file
|
||||||
|
- Exported web component is added.
|
||||||
|
|
||||||
|
If your project development needs a hot deployment of the frontend changes,
|
||||||
|
you can switch Flow to use Vite development server (default in Vaadin 23.3 and earlier versions):
|
||||||
|
- set `vaadin.frontend.hotdeploy=true` in `application.properties`
|
||||||
|
- configure `vaadin-maven-plugin`:
|
||||||
|
```
|
||||||
|
<configuration>
|
||||||
|
<frontendHotdeploy>true</frontendHotdeploy>
|
||||||
|
</configuration>
|
||||||
|
```
|
||||||
|
- configure `jetty-maven-plugin`:
|
||||||
|
```
|
||||||
|
<configuration>
|
||||||
|
<systemProperties>
|
||||||
|
<vaadin.frontend.hotdeploy>true</vaadin.frontend.hotdeploy>
|
||||||
|
</systemProperties>
|
||||||
|
</configuration>
|
||||||
|
```
|
||||||
|
|
||||||
|
Read more [about Vaadin development mode](https://vaadin.com/docs/next/flow/configuration/development-mode#precompiled-bundle).
|
BIN
src/main/bundles/dev.bundle
Normal file
BIN
src/main/bundles/dev.bundle
Normal file
Binary file not shown.
23
src/main/frontend/index.html
Normal file
23
src/main/frontend/index.html
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<!--
|
||||||
|
This file is auto-generated by Vaadin.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||||
|
<style>
|
||||||
|
html, body, #outlet {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<!-- index.ts is included here automatically (either by the dev server or during the build) -->
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- This outlet div is where the views are rendered -->
|
||||||
|
<div id="outlet"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1 +1,16 @@
|
||||||
/* Add your styles here */
|
/* Add your styles here */
|
||||||
|
|
||||||
|
.warning {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
justify-items: center;
|
||||||
|
background-color: var(--lumo-warning-color);
|
||||||
|
color: var(--lumo-warning-text-color);
|
||||||
|
box-shadow: var(--lumo-box-shadow-s);
|
||||||
|
font-size: var(--lumo-font-size-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: var(--lumo-font-size-l);
|
||||||
|
padding-top: var(--lumo-space-l);
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
{
|
{
|
||||||
"lumoImports": [
|
"lumoImports" : [ "typography", "color", "spacing", "badge", "utility" ],
|
||||||
"typography",
|
"autoInjectComponents" : "false"
|
||||||
"color",
|
}
|
||||||
"spacing",
|
|
||||||
"badge",
|
|
||||||
"utility"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
package com.example.base.ui;
|
|
||||||
|
|
||||||
import com.vaadin.flow.component.Component;
|
|
||||||
import com.vaadin.flow.component.notification.Notification;
|
|
||||||
import com.vaadin.flow.component.notification.NotificationVariant;
|
|
||||||
import com.vaadin.flow.server.VaadinServiceInitListener;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
class MainErrorHandler {
|
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(MainErrorHandler.class);
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public VaadinServiceInitListener errorHandlerInitializer() {
|
|
||||||
return (event) -> event.getSource().addSessionInitListener(
|
|
||||||
sessionInitEvent -> sessionInitEvent.getSession().setErrorHandler(errorEvent -> {
|
|
||||||
log.error("An unexpected error occurred", errorEvent.getThrowable());
|
|
||||||
errorEvent.getComponent().flatMap(Component::getUI).ifPresent(ui -> {
|
|
||||||
var notification = new Notification(
|
|
||||||
"An unexpected error has occurred. Please try again later.");
|
|
||||||
notification.addThemeVariants(NotificationVariant.LUMO_ERROR);
|
|
||||||
notification.setPosition(Notification.Position.TOP_CENTER);
|
|
||||||
notification.setDuration(3000);
|
|
||||||
ui.access(notification::open);
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
package com.example.base.ui;
|
|
||||||
|
|
||||||
import com.vaadin.flow.component.applayout.AppLayout;
|
|
||||||
import com.vaadin.flow.component.html.Div;
|
|
||||||
import com.vaadin.flow.component.html.Span;
|
|
||||||
import com.vaadin.flow.component.icon.Icon;
|
|
||||||
import com.vaadin.flow.component.icon.VaadinIcon;
|
|
||||||
import com.vaadin.flow.component.orderedlayout.Scroller;
|
|
||||||
import com.vaadin.flow.component.sidenav.SideNav;
|
|
||||||
import com.vaadin.flow.component.sidenav.SideNavItem;
|
|
||||||
import com.vaadin.flow.router.Layout;
|
|
||||||
import com.vaadin.flow.server.menu.MenuConfiguration;
|
|
||||||
import com.vaadin.flow.server.menu.MenuEntry;
|
|
||||||
|
|
||||||
import static com.vaadin.flow.theme.lumo.LumoUtility.*;
|
|
||||||
|
|
||||||
@Layout
|
|
||||||
public final class MainLayout extends AppLayout {
|
|
||||||
|
|
||||||
MainLayout() {
|
|
||||||
setPrimarySection(Section.DRAWER);
|
|
||||||
addToDrawer(createHeader(), new Scroller(createSideNav()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Div createHeader() {
|
|
||||||
// TODO Replace with real application logo and name
|
|
||||||
var appLogo = VaadinIcon.CUBES.create();
|
|
||||||
appLogo.addClassNames(TextColor.PRIMARY, IconSize.LARGE);
|
|
||||||
|
|
||||||
var appName = new Span("App");
|
|
||||||
appName.addClassNames(FontWeight.SEMIBOLD, FontSize.LARGE);
|
|
||||||
|
|
||||||
var header = new Div(appLogo, appName);
|
|
||||||
header.addClassNames(Display.FLEX, Padding.MEDIUM, Gap.MEDIUM, AlignItems.CENTER);
|
|
||||||
return header;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SideNav createSideNav() {
|
|
||||||
var nav = new SideNav();
|
|
||||||
nav.addClassNames(Margin.Horizontal.MEDIUM);
|
|
||||||
MenuConfiguration.getMenuEntries().forEach(entry -> nav.addItem(createSideNavItem(entry)));
|
|
||||||
return nav;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SideNavItem createSideNavItem(MenuEntry menuEntry) {
|
|
||||||
if (menuEntry.icon() != null) {
|
|
||||||
return new SideNavItem(menuEntry.title(), menuEntry.path(), new Icon(menuEntry.icon()));
|
|
||||||
} else {
|
|
||||||
return new SideNavItem(menuEntry.title(), menuEntry.path());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
package com.example.base.ui.component;
|
|
||||||
|
|
||||||
import com.vaadin.flow.component.Component;
|
|
||||||
import com.vaadin.flow.component.Composite;
|
|
||||||
import com.vaadin.flow.component.applayout.DrawerToggle;
|
|
||||||
import com.vaadin.flow.component.html.Div;
|
|
||||||
import com.vaadin.flow.component.html.H1;
|
|
||||||
import com.vaadin.flow.component.html.Header;
|
|
||||||
import com.vaadin.flow.theme.lumo.LumoUtility.*;
|
|
||||||
import org.jspecify.annotations.NullMarked;
|
|
||||||
import org.jspecify.annotations.Nullable;
|
|
||||||
|
|
||||||
@NullMarked
|
|
||||||
public final class ViewToolbar extends Composite<Header> {
|
|
||||||
|
|
||||||
public ViewToolbar(@Nullable String viewTitle, Component... components) {
|
|
||||||
addClassNames(Display.FLEX, FlexDirection.COLUMN, JustifyContent.BETWEEN, AlignItems.STRETCH, Gap.MEDIUM,
|
|
||||||
FlexDirection.Breakpoint.Medium.ROW, AlignItems.Breakpoint.Medium.CENTER);
|
|
||||||
|
|
||||||
var drawerToggle = new DrawerToggle();
|
|
||||||
drawerToggle.addClassNames(Margin.NONE);
|
|
||||||
|
|
||||||
var title = new H1(viewTitle);
|
|
||||||
title.addClassNames(FontSize.XLARGE, Margin.NONE, FontWeight.LIGHT);
|
|
||||||
|
|
||||||
var toggleAndTitle = new Div(drawerToggle, title);
|
|
||||||
toggleAndTitle.addClassNames(Display.FLEX, AlignItems.CENTER);
|
|
||||||
getContent().add(toggleAndTitle);
|
|
||||||
|
|
||||||
if (components.length > 0) {
|
|
||||||
var actions = new Div(components);
|
|
||||||
actions.addClassNames(Display.FLEX, FlexDirection.COLUMN, JustifyContent.BETWEEN, Flex.GROW, Gap.SMALL,
|
|
||||||
FlexDirection.Breakpoint.Medium.ROW);
|
|
||||||
getContent().add(actions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Component group(Component... components) {
|
|
||||||
var group = new Div(components);
|
|
||||||
group.addClassNames(Display.FLEX, FlexDirection.COLUMN, AlignItems.STRETCH, Gap.SMALL,
|
|
||||||
FlexDirection.Breakpoint.Medium.ROW, AlignItems.Breakpoint.Medium.CENTER);
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,86 +0,0 @@
|
||||||
package com.example.examplefeature;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import org.jspecify.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "task")
|
|
||||||
public class Task {
|
|
||||||
|
|
||||||
public static final int DESCRIPTION_MAX_LENGTH = 300;
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.SEQUENCE)
|
|
||||||
@Column(name = "task_id")
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
@Column(name = "description", nullable = false, length = DESCRIPTION_MAX_LENGTH)
|
|
||||||
private String description = "";
|
|
||||||
|
|
||||||
@Column(name = "creation_date", nullable = false)
|
|
||||||
private Instant creationDate;
|
|
||||||
|
|
||||||
@Column(name = "due_date")
|
|
||||||
@Nullable
|
|
||||||
private LocalDate dueDate;
|
|
||||||
|
|
||||||
protected Task() { // To keep Hibernate happy
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task(String description, Instant creationDate) {
|
|
||||||
setDescription(description);
|
|
||||||
this.creationDate = creationDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable Long getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDescription() {
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDescription(String description) {
|
|
||||||
if (description.length() > DESCRIPTION_MAX_LENGTH) {
|
|
||||||
throw new IllegalArgumentException("Description length exceeds " + DESCRIPTION_MAX_LENGTH);
|
|
||||||
}
|
|
||||||
this.description = description;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Instant getCreationDate() {
|
|
||||||
return creationDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable LocalDate getDueDate() {
|
|
||||||
return dueDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDueDate(@Nullable LocalDate dueDate) {
|
|
||||||
this.dueDate = dueDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (obj == null || !getClass().isAssignableFrom(obj.getClass())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (obj == this) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Task other = (Task) obj;
|
|
||||||
return getId() != null && getId().equals(other.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
// Hashcode should never change during the lifetime of an object. Because of
|
|
||||||
// this we can't use getId() to calculate the hashcode. Unless you have sets
|
|
||||||
// with lots of entities in them, returning the same hashcode should not be a
|
|
||||||
// problem.
|
|
||||||
return getClass().hashCode();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
package com.example.examplefeature;
|
|
||||||
|
|
||||||
import org.springframework.data.domain.Pageable;
|
|
||||||
import org.springframework.data.domain.Slice;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
|
||||||
|
|
||||||
interface TaskRepository extends JpaRepository<Task, Long>, JpaSpecificationExecutor<Task> {
|
|
||||||
|
|
||||||
// If you don't need a total row count, Slice is better than Page as it only performs a select query.
|
|
||||||
// Page performs both a select and a count query.
|
|
||||||
Slice<Task> findAllBy(Pageable pageable);
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
package com.example.examplefeature;
|
|
||||||
|
|
||||||
import org.jspecify.annotations.Nullable;
|
|
||||||
import org.springframework.data.domain.Pageable;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class TaskService {
|
|
||||||
|
|
||||||
private final TaskRepository taskRepository;
|
|
||||||
|
|
||||||
TaskService(TaskRepository taskRepository) {
|
|
||||||
this.taskRepository = taskRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
public void createTask(String description, @Nullable LocalDate dueDate) {
|
|
||||||
if ("fail".equals(description)) {
|
|
||||||
throw new RuntimeException("This is for testing the error handler");
|
|
||||||
}
|
|
||||||
var task = new Task(description, Instant.now());
|
|
||||||
task.setDueDate(dueDate);
|
|
||||||
taskRepository.saveAndFlush(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
public List<Task> list(Pageable pageable) {
|
|
||||||
return taskRepository.findAllBy(pageable).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
/**
|
|
||||||
* This is a feature package for the Task Management sample feature. Its purpose is to demonstrate how you typically
|
|
||||||
* structure Vaadin business applications, and how the different building blocks interact.
|
|
||||||
* <p>
|
|
||||||
* A feature package represents a self-contained unit of functionality, including UI components, business logic, and
|
|
||||||
* data access. It could be a subdomain or bounded context (e.g., "Billing"), a specific use case (e.g., "User
|
|
||||||
* Registration"), or even a complex UI view (e.g., "Dashboard").
|
|
||||||
* </p>
|
|
||||||
* <p>
|
|
||||||
* If your application is very small, you may not need dedicated feature packages. In that case, move the subpackages
|
|
||||||
* directly to the application package.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
@NullMarked
|
|
||||||
package com.example.examplefeature;
|
|
||||||
// TODO Remove this package once you have added real features
|
|
||||||
|
|
||||||
import org.jspecify.annotations.NullMarked;
|
|
|
@ -1,83 +0,0 @@
|
||||||
package com.example.examplefeature.ui;
|
|
||||||
|
|
||||||
import com.example.base.ui.component.ViewToolbar;
|
|
||||||
import com.example.examplefeature.Task;
|
|
||||||
import com.example.examplefeature.TaskService;
|
|
||||||
import com.vaadin.flow.component.button.Button;
|
|
||||||
import com.vaadin.flow.component.button.ButtonVariant;
|
|
||||||
import com.vaadin.flow.component.datepicker.DatePicker;
|
|
||||||
import com.vaadin.flow.component.grid.Grid;
|
|
||||||
import com.vaadin.flow.component.html.Main;
|
|
||||||
import com.vaadin.flow.component.notification.Notification;
|
|
||||||
import com.vaadin.flow.component.notification.NotificationVariant;
|
|
||||||
import com.vaadin.flow.component.textfield.TextField;
|
|
||||||
import com.vaadin.flow.router.Menu;
|
|
||||||
import com.vaadin.flow.router.PageTitle;
|
|
||||||
import com.vaadin.flow.router.Route;
|
|
||||||
import com.vaadin.flow.theme.lumo.LumoUtility;
|
|
||||||
|
|
||||||
import java.time.ZoneId;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.time.format.FormatStyle;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import static com.vaadin.flow.spring.data.VaadinSpringDataHelpers.toSpringPageRequest;
|
|
||||||
|
|
||||||
@Route("")
|
|
||||||
@PageTitle("Task List")
|
|
||||||
@Menu(order = 0, icon = "vaadin:clipboard-check", title = "Task List")
|
|
||||||
class TaskListView extends Main {
|
|
||||||
|
|
||||||
private final TaskService taskService;
|
|
||||||
|
|
||||||
final TextField description;
|
|
||||||
final DatePicker dueDate;
|
|
||||||
final Button createBtn;
|
|
||||||
final Grid<Task> taskGrid;
|
|
||||||
|
|
||||||
TaskListView(TaskService taskService) {
|
|
||||||
this.taskService = taskService;
|
|
||||||
|
|
||||||
description = new TextField();
|
|
||||||
description.setPlaceholder("What do you want to do?");
|
|
||||||
description.setAriaLabel("Task description");
|
|
||||||
description.setMaxLength(Task.DESCRIPTION_MAX_LENGTH);
|
|
||||||
description.setMinWidth("20em");
|
|
||||||
|
|
||||||
dueDate = new DatePicker();
|
|
||||||
dueDate.setPlaceholder("Due date");
|
|
||||||
dueDate.setAriaLabel("Due date");
|
|
||||||
|
|
||||||
createBtn = new Button("Create", event -> createTask());
|
|
||||||
createBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
|
||||||
|
|
||||||
var dateTimeFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(getLocale())
|
|
||||||
.withZone(ZoneId.systemDefault());
|
|
||||||
var dateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(getLocale());
|
|
||||||
|
|
||||||
taskGrid = new Grid<>();
|
|
||||||
taskGrid.setItems(query -> taskService.list(toSpringPageRequest(query)).stream());
|
|
||||||
taskGrid.addColumn(Task::getDescription).setHeader("Description");
|
|
||||||
taskGrid.addColumn(task -> Optional.ofNullable(task.getDueDate()).map(dateFormatter::format).orElse("Never"))
|
|
||||||
.setHeader("Due Date");
|
|
||||||
taskGrid.addColumn(task -> dateTimeFormatter.format(task.getCreationDate())).setHeader("Creation Date");
|
|
||||||
taskGrid.setSizeFull();
|
|
||||||
|
|
||||||
setSizeFull();
|
|
||||||
addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX, LumoUtility.FlexDirection.COLUMN,
|
|
||||||
LumoUtility.Padding.MEDIUM, LumoUtility.Gap.SMALL);
|
|
||||||
|
|
||||||
add(new ViewToolbar("Task List", ViewToolbar.group(description, dueDate, createBtn)));
|
|
||||||
add(taskGrid);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createTask() {
|
|
||||||
taskService.createTask(description.getValue(), dueDate.getValue());
|
|
||||||
taskGrid.getDataProvider().refreshAll();
|
|
||||||
description.clear();
|
|
||||||
dueDate.clear();
|
|
||||||
Notification.show("Task added", 3000, Notification.Position.BOTTOM_END)
|
|
||||||
.addThemeVariants(NotificationVariant.LUMO_SUCCESS);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
31
src/main/java/com/example/views/TestView.java
Normal file
31
src/main/java/com/example/views/TestView.java
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package com.example.views;
|
||||||
|
|
||||||
|
import com.vaadin.flow.component.checkbox.Checkbox;
|
||||||
|
import com.vaadin.flow.component.html.Div;
|
||||||
|
import com.vaadin.flow.component.html.H1;
|
||||||
|
import com.vaadin.flow.router.Route;
|
||||||
|
import com.vaadin.flow.router.RouterLayout;
|
||||||
|
|
||||||
|
@Route("")
|
||||||
|
public class TestView extends Div implements RouterLayout {
|
||||||
|
|
||||||
|
public TestView() {
|
||||||
|
|
||||||
|
final var warning = new Div("This is a warning message.");
|
||||||
|
warning.setClassName("warning");
|
||||||
|
add(warning);
|
||||||
|
|
||||||
|
add(new H1("Lumo Theme Test"));
|
||||||
|
|
||||||
|
final var darkModeSwitcher = new Checkbox("Dark mode");
|
||||||
|
add(darkModeSwitcher);
|
||||||
|
darkModeSwitcher.addValueChangeListener(event -> {
|
||||||
|
if (event.getValue()) {
|
||||||
|
getElement().getThemeList().add("dark");
|
||||||
|
} else {
|
||||||
|
getElement().getThemeList().remove("dark");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue