Spring Boot, Vaadin을 이용한 서버 사이드 웹 UI 제작하기

Vaadin Framework

  • Vaadin Framework(바딘 프레임워크)는 Java 기반의 웹 앱 UI 프레임워크이다. Vaddin의 장점은 클라이언트 사이드에서의 HTML, JavaScript, CSS에 대한 깊이 있는 지식이 없어도 서버 사이드에서 Java 소스 코드 작성 만으로 웹 브라우저에서 작동하는 HTML5 웹 애플리케이션을 제작할 수 있다. 모든 것은 Vaadin이 대신 처리해준다. 특히 VaadinSpring Boot을 결합하면 상당한 생산성 향상이 가능하다.
  • Vaadin의 단점은 이런 류의 UI 종합선물세트가 모두 그러하듯 커스터마이징의 자유도가 떨어진다는 것이다. 웹 디자이너, 퍼블리셔와의 광범위한 협업과 풍부한 자유도가 요구되는 프로젝트에는 어울리지 않는다. 특정 비즈니스 로직 처리에 초점을 맞춘 어드민 또는 대시보드 애플리케이션에 적합하다. 유명한 NAS 운영체제 어드민 툴인 Synology NAS DSMVaadin이 추구하는 앱의 형태와 유사하다.
  • Vaadin FrameworkApache 2.0 라이센스로 무료로 제공된다. 아래 설명할 Vaadin Elements의 일부와 Vaadin Directory 또한 무료로 제공된다.

Vaadin Elements

  • Vaadin이 제공하는 Element는 일종의 위젯 또는 컴포넌트를 의미한다. 총 42개의 Element를 제공하며 이 중 무료로 제공되는 것은 14개이다. 무료로 제공되는 목록은 아래와 같다.
vaadin-button
vaadin-checkbox
vaadin-combo-box
vaadin-context-menu
vaadin-date-picker
vaadin-dialog
vaadin-form-layout
vaadin-grid
vaadin-icons
vaadin-progress-bar
vaadin-radio-button
vaadin-split-layout
vaadin-text-field
vaadin-upload
  • 유료로 제공되는 목록은 아래와 같다. 크게 Board, Spreadsheet, Chart로 구분된다.
vaadin-board
vaadin-spreadsheet
vaadin-area-chart
vaadin-arearange-chart
vaadin-areaspline-chart
vaadin-areasplinerange-chart
vaadin-bar-chart
vaadin-boxplot-chart
vaadin-bubble-chart
vaadin-candlestick-chart
vaadin-column-chart
vaadin-columnrange-chart
vaadin-errorbar-chart
vaadin-flags-chart
vaadin-funnel-chart
vaadin-gauge-chart
vaadin-heatmap-chart
vaadin-line-chart
vaadin-ohlc-chart
vaadin-pie-chart
vaadin-polygon-chart
vaadin-pyramid-chart
vaadin-scatter-chart
vaadin-solidgauge-chart
vaadin-sparkline
vaadin-spline-chart
vaadin-treemap-chart
vaadin-waterfall-chart

VerticalLayout, HorizontalLayout

  • 각 컴포넌트는 독립적인 오브젝트이다. 이들 컴포넌트를 화면에 원하는 위치에 배치하려면 레이아웃으로 감싸야 한다. VerticalLayout은 컴포넌트를 추가한 순서대로 위에서 아래로 배치하는 기능을 한다. 반대로 HorizontalLayout는 컴포넌트를 추가한 순서대로 왼쪽으로 오른쪽으로 배치한다.
// 버티컬 레이아웃 컴포넌트를 생성
VerticalLayout mainLayout = new VerticalLayout();

// 마진(외곽 여백)을 0으로 설정
mainLayout.setMargin(false);

// 레이아웃에 배치될 컴포넌트 추가
mainLayout.addComponent({레이아웃에 추가할 컴포넌트 오브젝트});

FormLayout

  • FormLayout은 라벨과 텍스트박스를 한 쌍으로 하는 입력 폼에 특화된 레이아웃이다. 컴포넌트를 추가한 순서대로 위에서 아래로 배치된다.
FormLayout loginFormLayout = new FormLayout();

// 텍스트필드를 생성
TextField usernameTextField = new TextField("Username");

// 텍스트필드 앞에 출력될 아이콘을 설정
usernameTextField.setIcon(VaadinIcons.USER);

// 입력 필수 기호를 출력
usernameTextField.setRequiredIndicatorVisible(true);

loginFormLayout.addComponent(usernameTextField);

TextField passwordTextField = new TextField("Password");
passwordTextField.setIcon(VaadinIcons.PASSWORD);
passwordTextField.setRequiredIndicatorVisible(true);
loginFormLayout.addComponent(passwordTextField);

// 버튼을 생성
Button loginButton = new Button("Login");
loginButton.addStyleName(ValoTheme.BUTTON_ICON_ALIGN_RIGHT);
loginButton.setIcon(VaadinIcons.CONNECT);
loginFormLayout.addComponent(loginButton);

Notification

  • Notification은 화면 중앙에 메시지를 출력하는 기능을 한다. 별도의 컴포넌트로 분류되지 않지만 화면에서 중요한 역할을 한다.
// 단순 정보 메시지를 출력
Notification.show(
    "INFO",
    "info message",
    Notification.Type.HUMANIZED_MESSAGE
);

// 경고 메시지를 출력
Notification.show(
    "WARNING",
    "warning message",
    Notification.Type.WARNING_MESSAGE
);

// 에러 메시지를 출력
Notification.show(
    "ERROR",
    "error message",
    Notification.Type.ERROR_MESSAGE
);

Button

  • Button 컴포넌트는 아래와 같이 생성한다.
// 버튼 컴포넌트를 생성하고 출력될 문자열을 설정
Button loginButton = new Button("Login");

// 버튼 스타일을 설정
// BUTTON_TINY
// BUTTON_SMALL
// BUTTON_LARGE
// BUTTON_HUGE
// BUTTON_BORDERLESS
// BUTTON_BORDERLESS_COLORED
// BUTTON_QUIET
// BUTTON_DANGER
// BUTTON_FRIENDLY
// BUTTON_PRIMARY
// BUTTON_ICON_ALIGN_RIGHT
// BUTTON_ICON_ALIGN_TOP
// BUTTON_ICON_ONLY
loginButton.addStyleName(ValoTheme.BUTTON_ICON_ALIGN_RIGHT);

// 버튼 아이콘을 설정
loginButton.setIcon(VaadinIcons.BROWSER);

// 버튼 클릭 이벤트를 작성
loginButton.addClickListener(new Button.ClickListener() {
    @Override
    public void buttonClick(Button.ClickEvent event) {
        Notification.show(
            "INFO",
            "button clicked!",
            Notification.Type.HUMANIZED_MESSAGE
        );
    }
});

Grid

  • Grid는 애플리케이션에서 CRUD의 기본이 되는 컴포넌트이다.
// 사용자 정보를 가지는 User 목록을 로드했다고 가정
List<User> users = userService.getUsers();

// Grid 생성
Grid<User> userGrid = new Grid<>();

// Grid에 데이터를 바인딩
userGrid.setItems(users);

// Grid에 출력될 컬럼을 설정
userGrid.addColumn(User::getName).setCaption("Name");
userGrid.addColumn(User::getRole).setCaption("Role");

// 로우 선택 방식을 싱글로 설정
userGrid.setSelectionMode(SelectionMode.SINGLE);

// 로우 선택 방식을 멀티로 설정
userGrid.setSelectionMode(SelectionMode.MULTI);

// 그리드의 높이를 설정
userGrid.setHeightByRows(10);

// 현재 선택된 로우를 반환
Set<User> selectedUsers = userGrid.getSelectedItems();

// 현재 선택된 로우를 모두 해제
userGrid.deselectAll();
  • MenuBar는 일반적인 데스크탑 애플리케이션의 메뉴로 기능하는 컴포넌트이다. 아래와 같이 생성한다.
// 메뉴바 컴포넌트를 생성
MenuBar mainMenuBar = new MenuBar();

// 메뉴바 스타일을 설정 (MENUBAR_BORDERLESS, MENUBAR_SMALL)
mainMenuBar.addStyleName(ValoTheme.MENUBAR_SMALL);

// 메뉴바 메뉴 선택 이벤트를 작성
Command menuCommand = new Command() {
    public void menuSelected(MenuItem selectedItem) {
        ...
    }
}

// 메뉴바에 출력될 메인 메뉴를 생성
MenuBar.MenuItem firstMenuItem = mainMenuBar.addItem("First", null, menuCommand);
MenuBar.MenuItem secondMenuItem = mainMenuBar.addItem("Second", null, menuCommand);
MenuBar.MenuItem thirdMenuItem = mainMenuBar.addItem("Third", null, menuCommand);

// 각 메인 메뉴 하단에 출력될 서브 메뉴를 생성
MenuBar.MenuItem subFirstMenuItem = firstMenuItem.addItem("Sub-First", null, menuCommand);
firstMenuItem.addSeparator();
MenuBar.MenuItem subSecondMenuItem = firstMenuItem.addItem("Sub-Second", null, menuCommand);

Vaadin Directory

  • 앞서 소개한 Vaadin Elements가 제작사가 제공하는 유무료 위젯이었다면 사용자들이 제작하여 배포하는 위젯이 있는데 이를 Vaadin Directory라고 부른다. 유료로 제공되는 챠트 위젯의 경우 사용자가 제작하여 공개하는 Chart.js Add-on으로 대체할 수 있다.

Vaadin Spring Boot Starter

  • Spring Bootvaadin-spring-boot-starter 패키지를 제공하여 편리하게 Spring Boot + Vaadin 기반의 프로젝트를 생성할 수 있다. (vaadin-spring-boot-starter 패키지는 spring-boot-starter-web 패키지를 포함한다.)
  • 프로젝트를 생성하는 방법은 다양하지만 별도의 도구 없이 간편하게 Spring Initializr에 접속하여 마우스 클릭 만으로 기본 골격 프로젝트를 생성할 수 있다. Dependencies 항목에서 Vaadin을 추가하고 Generate Project를 클릭하면 프로젝트 파일을 다운로드할 수 있다. 압축을 해제하고 build.gradle 파일을 IDE에서 불러오면 된다.

@SpringUI

  • 프로젝트 기본 골격이 구성되었으니 첫 시작으로 애플리케이션의 시작 화면에 해당하는 클래스를 작성해야 한다. 아래와 같이 작성한다.
package com.example.demo;

import com.vaadin.annotations.Theme;
import com.vaadin.annotations.Title;
import com.vaadin.server.VaadinRequest;
import com.vaadin.spring.annotation.SpringUI;
import com.vaadin.ui.Button;
import com.vaadin.ui.UI;

@SpringUI(path = "/")
@SpringViewDisplay
@Title("Hello, World!")
@Theme("valo")
public class HelloUI extends UI implements ViewDisplay {

    private Panel springViewDisplay;

    @Override
    protected void init(VaadinRequest request) {

        setContent(new Button("Hello, World!"));
    }
    @Override
    public void showView(View view) {

        springViewDisplay.setContent((Component) view);
    }
}
  • com.vaadin.ui.UI 클래스의 서브 클래스를 작성하고 @SpringUI 어노테이션을 명시하면 하나의 화면 단위로 기능하게 된다. path 속성에는 해당 화면을 웹에서 접근 가능한 경로를 명시한다. 위 예제의 경우 프로젝트를 구동하고 브라우저에서 http://localhost:8080에 접속하면 Hello, World! 버튼이 화면에 출력되는 것을 확인할 수 있다.
  • @Title 어노테이션에는 해당 화면의 제목을 명시한다. 또한, @Theme 어노테이션을 통해 화면 단위로 테마를 명시할 수 있다. 기본 공식 테마로 Valo 테마가 제공된다.
  • @SpringUI 어노테이션이 명시된 클래스는 특정 웹 경로를 해당 화면으로 라우팅하고 렌더링해주는데 특화된 @Controller라고 볼 수 있다. 스프링의 다른 빈과 동일하게 싱글턴 빈으로 취급되므로 스프링의 편리함과 익숙함을 그대로 적용할 수 있다.
  • @SprinViewDisplay 어노테이션은 Navigator의 사용을 가능하게 해준다. Navigator를 사용하면 물리적으로 단일 진입점을 가진 하나의 UI에서 SPA와 같은 효과를 줄 수 있다. @SpringViewDisplay은 프로젝트 내에서 오직 1개의 UI 서브 클래스에만 명시할 수 있다.
// 바로 전 뷰로 이동
getUI().getNavigator().navigateBack();

// #!user_management 뷰로 이동
getUI().getNavigator().navigateTo("user_management");
  • UI의 서브 클래스는에서는 아래와 같은 기능을 수행할 수 있다.
  • // 특정 URI로 리다이렉트
    getPage().setLocation("/login");
    
    // 현재 세션의 특정 애트리뷰트 설정
    getSession().setAttribute("user_id", userId);
    
    // 현재 세션의 특정 애트리뷰트 획득
    Object user_id = getSession().getAttribute("user_id");
    
    // 현재 세션 종료, 주로 사용자 로그아웃 시점에 실행
    getSession().close();
    

    Material Theme

    • Vaadin은 기본 테마로 Valo만을 제공한다. 개인이 애드온으로 제작한 Material 테마를 적용할 수 있다. 먼저 build.gradle 파일에 아래 정보를 추가한다.
    dependencies {
        compile group: 'com.github.appreciated', name: 'material', version: '1.1.7'
    }
    
    • 아래와 같이 테마를 적용할 UI 서브 클래스에 material을 명시하면 테마가 적용된다.
    @Theme("material")
    public class HelloUI extends UI {