Vaadin
Вот полный рабочий пример использования Java с Vaadin Framework версии 8.5.2 для создания веб-приложения, в котором база данных, управляемая H2 Database Engine , отслеживает список продуктов ( 10 планет Солнечной системы). Виджет NativeSelect
в Vaadin заполняется из List
из Product
объектов, загруженных из таблицы product_
в этой базе данных. Каждый раз, когда пользователь нажимает кнопку Order
, заказ записывается в виде строки в таблице order_
.
Вот простая диаграмма ERD для двух таблиц в базе данных.
Пользователь сначала выбирает продукт планеты из выпадающего списка, затем нажимает кнопку «Заказать».
Под этой областью ввода данных вы видите пару Grid
виджетов как задний ход в базу данных. Слева находится список товаров, который не меняется. Справа находится список всех заказов, который обновляется каждый раз, когда пользователь размещает заказ с помощью этой кнопки «Заказ».
Обратите внимание, что база данных находится в памяти, а не является постоянной, поскольку это всего лишь небольшая демонстрация. Поэтому каждый раз, когда вы запускаете приложение, база данных перестраивается с нуля. Таблица order_
начинается пустым при каждом запуске.
Наконец, это ни в коем случае не готовый к работе код, просто пример, демонстрирующий возможности. Только сейчас, когда снимал этот скриншот, я обнаружил ошибку, связанную с отсутствием выбора продукта. C’est la vie.
Подробнее см. В следующих разделах руководства Vaadin :
- Кнопка - Обзор виджета кнопки.
- NativeSelect - Быстрая демонстрация этого виджета выпадающего списка
- Компоненты выбора - Обсуждение работы виджетов, таких как
NativeSelect
, в Vaadin
- Сетка - Как использовать этот мощный виджет сетки данных.
Основной класс приложения
В основном шаблон, чтобы заставить Ваадина бежать. Важна пара линий посередине, для ProductPickerLayout
.
package com.basilbourque.example;
import javax.servlet.annotation.WebServlet;
import com.vaadin.annotations.Theme;
import com.vaadin.annotations.VaadinServletConfiguration;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinServlet;
import com.vaadin.ui.*;
/**
* This UI is the application entry point. A UI may either represent a browser window
* (or tab) or some part of an HTML page where a Vaadin application is embedded.
* <p>
* The UI is initialized using {@link #init(VaadinRequest)}. This method is intended to be
* overridden to add component to the user interface and initialize non-component functionality.
*/
@Theme ( "mytheme" )
public class MyUI extends UI {
@Override
protected void init ( VaadinRequest vaadinRequest ) {
final Layout layout = new ProductPickerLayout();
this.setContent( layout );
}
@WebServlet ( urlPatterns = "/*", name = "MyUIServlet", asyncSupported = true )
@VaadinServletConfiguration ( ui = MyUI.class, productionMode = false )
public static class MyUIServlet extends VaadinServlet {
}
}
Прослушиватель контекста сервлета
Реагирует на запуск и закрытие веб-приложения.
package com.basilbourque.example;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import java.time.Instant;
@WebListener
public class MyServletContextListener implements ServletContextListener {
MyDatabaseService databaseService;
@Override
public void contextInitialized ( ServletContextEvent servletContextEvent ) {
System.out.println( "TRACE - contextInitialized " + Instant.now() );
// Database.
MyDatabaseService db = new MyDatabaseService();
db.establishDatabase();
}
@Override
public void contextDestroyed ( ServletContextEvent servletContextEvent ) {
// This method intentionally left blank.
}
}
База данных класса обслуживания
Определяет и предварительно загружает базу данных. Предоставляет вставки и запросы для перемещения данных в базу данных и из нее.
package com.basilbourque.example;
import java.sql.*;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class MyDatabaseService {
// ---------| Members |------------------------------------
static final private String driverName = "org.h2.Driver";
static final private String catalogName = "ProdPop";
static final private String jdbcPath = "jdbc:h2:mem:" + MyDatabaseService.catalogName + ";DB_CLOSE_DELAY=-1"; // "jdbc:h2:mem:autogrid"; // Set delay to keep in-memory database even after last connection closed.
static final private String productTableName = "product_";
static final private String orderTableName = "order_";
public void establishDatabase () {
// Verify JDBC driver.
try {
Class.forName( MyDatabaseService.driverName );
} catch ( ClassNotFoundException e ) {
e.printStackTrace();
}
// Connect, and create database.
try ( Connection conn = DriverManager.getConnection( MyDatabaseService.jdbcPath ) ;
) {
String sql = null;
// Create product_ table.
// Columns: pkey_ name_
try ( Statement stmt = conn.createStatement() ; ) {
sql = "CREATE TABLE " + productTableName + " ( \n" +
" pkey_ IDENTITY PRIMARY KEY , \n" +
" name_ VARCHAR ( 80 ) NOT NULL \n" +
") ; \n";
System.out.println( "TRACE - SQL:\n" + sql );
stmt.execute( sql );
}
System.out.println( "TRACE - Created table product_." );
// Create order_ table.
// Columns: pkey_ fkey_product_ when_ordered_
try ( Statement stmt = conn.createStatement() ; ) {
sql = "CREATE TABLE " + orderTableName + " ( \n" +
" pkey_ IDENTITY PRIMARY KEY , \n" +
" fkey_product_ LONG NOT NULL , \n" +
" when_ordered_ TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP \n" +
") ; \n" +
"ALTER TABLE " + orderTableName + " ADD FOREIGN KEY ( fkey_product_ ) REFERENCES product_ ( pkey_ ) ; \n "
;
System.out.println( "TRACE - SQL:\n" + sql );
stmt.execute( sql );
}
// List tables
DatabaseMetaData md = conn.getMetaData();
try ( ResultSet rs = md.getTables( null , null , null , null ) ) {
while ( rs.next() ) {
System.out.println( rs.getString( 3 ) );
}
}
// List columns of `product_` table.
try ( ResultSet rs = md.getColumns( null , null , productTableName.toUpperCase( Locale.US ) , null ) ) {
System.out.println( "Columns of table: " + productTableName );
while ( rs.next() ) {
System.out.println( rs.getString( 4 ) + " | " + rs.getString( 5 ) + " | " + rs.getString( 6 ) ); // COLUMN_NAME, DATA_TYPE , TYPE_NAME.
}
}
// List columns of `order_` table.
try ( ResultSet rs = md.getColumns( null , null , orderTableName.toUpperCase( Locale.US ) , null ) ) {
System.out.println( "Columns of table: " + orderTableName );
while ( rs.next() ) {
System.out.println( rs.getString( 4 ) + " | " + rs.getString( 5 ) + " | " + rs.getString( 6 ) ); // COLUMN_NAME, DATA_TYPE , TYPE_NAME.
}
}
// Add rows
sql = "INSERT INTO product_ ( name_ ) \n" +
"VALUES ( ? ) " +
"; ";
List< String > planets = List.of( "Mercury" , "Venus" , "Earth" , "Mars" , "Ceres" , "Jupiter" , "Saturn" , "Uranus" , "Neptune" , "Pluto" );
try ( PreparedStatement ps = conn.prepareStatement( sql ) ; ) {
for ( String planet : planets ) {
ps.setString( 1 , planet );
ps.executeUpdate();
}
}
System.out.println( "TRACE - Dumping tables in their initial state. " + Instant.now() );
this.dumpTableToConsole( MyDatabaseService.productTableName );
this.dumpTableToConsole( MyDatabaseService.orderTableName );
} catch ( SQLException e ) {
e.printStackTrace();
}
}
public void dumpTableToConsole ( String tableName ) {
try ( Connection conn = DriverManager.getConnection( MyDatabaseService.jdbcPath ) ;
) {
System.out.println( "TRACE - « " + tableName + " » table dump to console at " + Instant.now() );
String sql = "SELECT * FROM " + tableName + " ;";
try ( Statement stmt = conn.createStatement() ;
ResultSet rs = stmt.executeQuery( sql ) ; ) {
ResultSetMetaData meta = rs.getMetaData();
int colCount = meta.getColumnCount();
int rowCount = 0;
while ( rs.next() ) {
rowCount++;
System.out.print( "Row # " + rowCount + ": " );
for ( int col = 1 ; col <= colCount ; col++ ) {
System.out.print( meta.getColumnLabel( col ) + "=" );
Object o = rs.getObject( col );
if ( null != o ) {
System.out.print( o.toString() + " " );
}
}
System.out.println( "" ); // Newline.
}
System.out.println( "« fin de " + tableName + " »" );
}
} catch ( SQLException e ) {
e.printStackTrace();
}
}
public List< Product > fetchAllProducts () {
System.out.println( "TRACE MyDatabaseService::fetchAllOrders at " + Instant.now() );
List< Product > products = new ArrayList<>();
// Query. Loop ResultSet, instantiating object, and collecting.
try ( Connection conn = DriverManager.getConnection( MyDatabaseService.jdbcPath ) ;
) {
System.out.println( "TRACE - fetchAllProducts at " + Instant.now() );
String sql = "SELECT * FROM " + productTableName + " ;";
try (
Statement stmt = conn.createStatement() ;
ResultSet rs = stmt.executeQuery( sql ) ;
) {
int rowCount = 0;
while ( rs.next() ) {
Long pkey = rs.getLong( "pkey_" );
String name = rs.getString( "name_" );
// Make object from column values.
Product p = new Product( pkey , name );
products.add( p ); // Collect each `Order` object retrieved from database.
}
}
} catch ( SQLException e ) {
e.printStackTrace();
}
return products;
}
public List< Order > fetchAllOrders () {
System.out.println( "TRACE MyDatabaseService::fetchAllOrders at " + Instant.now() );
List< Order > orders = new ArrayList<>();
// Query. Loop ResultSet, instantiating object, and collecting.
try ( Connection conn = DriverManager.getConnection( MyDatabaseService.jdbcPath ) ;
) {
String sql = "SELECT * FROM " + orderTableName + " \n ORDER BY pkey_ DESC \n ;";
try (
Statement stmt = conn.createStatement() ;
ResultSet rs = stmt.executeQuery( sql ) ;
) {
int rowCount = 0;
while ( rs.next() ) {
Long pkey = rs.getLong( "pkey_" );
Long fkey_product = rs.getLong( "fkey_product_" );
Instant when_ordered = rs.getObject( "when_ordered_" , Instant.class );
// Make object from column values.
Order o = new Order( pkey , fkey_product , when_ordered );
orders.add( o ); // Collect each `Order` object retrieved from database.
}
}
} catch ( SQLException e ) {
e.printStackTrace();
}
return orders;
}
public void insertOrder ( Long fkeyProduct ) {
System.out.println( "TRACE - MyDatabaseService::insertOrder at " + Instant.now() );
try ( Connection conn = DriverManager.getConnection( MyDatabaseService.jdbcPath ) ;
) {
String sql = "INSERT INTO " + orderTableName + "( fkey_product_ )\n" + " VALUES ( ? ) ;\n";
PreparedStatement ps = conn.prepareStatement( sql );
ps.setLong( 1 , fkeyProduct );
ps.executeUpdate();
} catch ( SQLException e ) {
e.printStackTrace();
}
}
}
Макет контента
Отображает наши виджеты: раскрывающийся список продуктов NativeSelect
, кнопка заказа и пара Grid
сеток данных. Служит видом и контроллером для взаимодействия с нашим пользователем.
package com.basilbourque.example;
import com.vaadin.data.HasValue;
import com.vaadin.data.HasValue.ValueChangeEvent;
import com.vaadin.ui.*;
import java.time.Instant;
import java.util.List;
public class ProductPickerLayout extends VerticalLayout {
Label prodPopLabel;
NativeSelect< Product > productSelect;
Button orderButton;
Grid< Product > productsGrid;
Grid< Order > ordersGrid;
// Constructor
public ProductPickerLayout () {
this.widgetsMake();
this.widgetsArrange();
}
private void widgetsMake () {
// Create the selection component
this.prodPopLabel = new Label( "Products: " );
this.productSelect = new NativeSelect<>();
// Add some items
List< Product > products = new MyDatabaseService().fetchAllProducts();
this.productSelect.setItems( products );
this.productSelect.setItemCaptionGenerator( Product :: getName );
// Show 5 items and a scrollbar if there are more
// select.setRows( 3 );
productSelect.addValueChangeListener( new HasValue.ValueChangeListener< Product >() {
@Override
public void valueChange ( ValueChangeEvent< Product > valueChangeEvent ) {
Product p = valueChangeEvent.getValue();
orderButton.setEnabled( null != p );
Notification.show( "Selected: " + p.name );
}
} );
this.orderButton = new Button( "Order" );
this.orderButton.setEnabled( this.productSelect.getValue() != null );
this.orderButton.addClickListener( ( Button.ClickEvent e ) -> {
this.placeOrder();
this.ordersGrid.setItems( new MyDatabaseService().fetchAllOrders() );
} );
MyDatabaseService db = new MyDatabaseService();
this.productsGrid = new Grid<>( Product.class );
this.productsGrid.setItems( products );
this.productsGrid.setCaption( "Products" );
this.ordersGrid = new Grid<>( Order.class );
List< Order > orders = db.fetchAllOrders();
this.ordersGrid.setItems( orders );
this.ordersGrid.setCaption( "Orders" );
}
private void widgetsArrange () {
HorizontalLayout orderBar = new HorizontalLayout();
orderBar.setSpacing( true );
orderBar.addComponents( this.prodPopLabel , this.productSelect , this.orderButton );
orderBar.setComponentAlignment( this.prodPopLabel , Alignment.MIDDLE_CENTER );
orderBar.setComponentAlignment( this.productSelect , Alignment.MIDDLE_CENTER );
orderBar.setComponentAlignment( this.orderButton , Alignment.MIDDLE_CENTER );
HorizontalLayout gridsBar = new HorizontalLayout();
gridsBar.setSpacing( true );
gridsBar.addComponents( this.productsGrid , this.ordersGrid );
this.addComponents( orderBar , gridsBar );
this.setExpandRatio( gridsBar , 1.0F );
}
private void placeOrder () {
// Get pkey of the currently selected product.
Product p = this.productSelect.getValue();
if ( null == p ) {
throw new IllegalStateException( "The `productSelect` NativeSelect does not have a current value." );
}
Long fkeyProduct = p.pkey;
// Insert row into table.
new MyDatabaseService().insertOrder( fkeyProduct );
this.updateOrdersGrid();
}
private void updateOrdersGrid () {
}
}
Модельные классы - Product
& Order
Методы доступа метода получения / установки автоматически обнаруживаются и используются Ваадином для отображения данных в виджетах сетки данных Grid
. Конечно, вместо этого вы можете выполнить ручную настройку.
Наш Product
класс.
package com.basilbourque.example;
public class Product {
Long pkey;
String name;
public Product ( Long pkey , String name ) {
this.pkey = pkey;
this.name = name;
}
@Override
public String toString () {
return "Product{ " +
"pkey=" + pkey +
"| name='" + name +
" }";
}
// -----------| Accessors |-----------------
public Long getPkey () {
return pkey;
}
public void setPkey ( Long pkey ) {
this.pkey = pkey;
}
public String getName () {
return name;
}
public void setName ( String name ) {
this.name = name;
}
}
Наш Order
класс.
package com.basilbourque.example;
import java.time.Instant;
public class Order {
Long pkey; // Identifier of this order.
Long fkeyProduct; // Identifier of the product being ordered.
Instant whenOrdered; // The moment the order was placed.
public Order ( Long pkey , Long fkeyProduct , Instant whenOrdered ) {
this.pkey = pkey;
this.fkeyProduct = fkeyProduct;
this.whenOrdered = whenOrdered;
}
@Override
public String toString () {
return "Order{ " +
"pkey=" + pkey +
"| fkeyProduct=" + fkeyProduct +
"| whenOrdered=" + whenOrdered +
" }";
}
// -----------| Accessors |-----------------
public Long getPkey () {
return pkey;
}
public void setPkey ( Long pkey ) {
this.pkey = pkey;
}
public Long getFkeyProduct () {
return fkeyProduct;
}
public void setFkeyProduct ( Long fkeyProduct ) {
this.fkeyProduct = fkeyProduct;
}
public Instant getWhenOrdered () {
return whenOrdered;
}
public void setWhenOrdered ( Instant whenOrdered ) {
this.whenOrdered = whenOrdered;
}
}
Maven POM
Файл pom.xml
, управляющий Maven для загрузки зависимостей (библиотек) и для создания / запуска веб-приложения.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.basilbourque.example</groupId>
<artifactId>prodpop</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>prodpop</name>
<prerequisites>
<maven>3</maven>
</prerequisites>
<properties>
<vaadin.version>8.5.2</vaadin.version>
<vaadin.plugin.version>8.5.2</vaadin.plugin.version>
<jetty.plugin.version>9.4.12.v20180830</jetty.plugin.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>10</maven.compiler.source>
<maven.compiler.target>10</maven.compiler.target>
<!-- If there are no local customizations, this can also be "fetch" or "cdn" -->
<vaadin.widgetset.mode>local</vaadin.widgetset.mode>
</properties>
<repositories>
<repository>
<id>vaadin-addons</id>
<url>http://maven.vaadin.com/vaadin-addons</url>
</repository>
</repositories>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-bom</artifactId>
<version>${vaadin.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-server</artifactId>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-push</artifactId>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-client-compiled</artifactId>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-themes</artifactId>
</dependency>
<!--Basil-->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.197</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
<!-- Exclude an unnecessary file generated by the GWT compiler. -->
<packagingExcludes>WEB-INF/classes/VAADIN/widgetsets/WEB-INF/**</packagingExcludes>
</configuration>
</plugin>
<plugin>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-maven-plugin</artifactId>
<version>${vaadin.plugin.version}</version>
<executions>
<execution>
<goals>
<goal>update-theme</goal>
<goal>update-widgetset</goal>
<goal>compile</goal>
<!-- Comment out compile-theme goal to use on-the-fly theme compilation -->
<goal>compile-theme</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
<!-- Clean up also any pre-compiled themes -->
<configuration>
<filesets>
<fileset>
<directory>src/main/webapp/VAADIN/themes</directory>
<includes>
<include>**/styles.css</include>
<include>**/styles.scss.cache</include>
</includes>
</fileset>
</filesets>
</configuration>
</plugin>
<!-- The Jetty plugin allows us to easily test the development build by
running jetty:run on the command line. -->
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>${jetty.plugin.version}</version>
<configuration>
<scanIntervalSeconds>2</scanIntervalSeconds>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<!-- Vaadin pre-release repositories -->
<id>vaadin-prerelease</id>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<repositories>
<repository>
<id>vaadin-prereleases</id>
<url>http://maven.vaadin.com/vaadin-prereleases</url>
</repository>
<repository>
<id>vaadin-snapshots</id>
<url>https://oss.sonatype.org/content/repositories/vaadin-snapshots/</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>vaadin-prereleases</id>
<url>http://maven.vaadin.com/vaadin-prereleases</url>
</pluginRepository>
<pluginRepository>
<id>vaadin-snapshots</id>
<url>https://oss.sonatype.org/content/repositories/vaadin-snapshots/</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
</project>
Кстати, в мире macOS «выпадающий список» известен как «всплывающее меню» или «всплывающее окно».