В кли­ент‑сер­верной архи­тек­туре мес­то Java при­ложе­ние - пре­иму­щес­твен­но на сер­верной сто­роне, при этом веб‑интерфейс пишет­ся отдель­ной груп­пой фронт‑енд раз­работ­чиков на JavaScript. Java не пред­лага­ет адек­ватных средств для соз­дания сов­ремен­ного веб‑интерфей­са (ког­да в пос­ледний раз ты видел Java-апплет..?) ни с точ­ки зре­ния дизай­на, ни с точ­ки зре­ния реали­зации кли­ент‑сер­верно­го вза­имо­дей­ствия.

А что, если бы все кли­ент‑сер­верное при­ложе­ние целиком писалось на Java, но его кли­ент­ская часть была бы «натив­ной» для бра­узе­ра и соот­ветс­тво­вала бы самым сов­ремен­ным пред­став­лени­ям о юза­били­ти?

 

Введение

Рис. 1. Логотип Vaadin
Рис. 1. Логотип Vaadin

Vaadin (кста­ти, в перево­де с фин­ско­го это сло­во озна­чает «оле­ниха») под­держи­вает все рас­простра­нен­ные бра­узе­ры как обыч­ных компь­юте­ров, так и мобиль­ных устрой­ств и план­шетов. Вся раз­работ­ка ведет­ся на Java, но Java-код выпол­няет­ся толь­ко на сер­вере, на кли­енте же выпол­няет­ся чис­тый JavaScript.

Струк­турно Vaadin сос­тоит из сер­верно­го API, кли­ент­ско­го API, набора ком­понен­тов поль­зователь­ско­го интерфей­са с обе­их сто­рон, механиз­ма тем для офор­мле­ния интерфей­са и модели дан­ных, поз­воля­ющей свя­зывать сер­верные ком­понен­ты непос­редс­твен­но с дан­ными. Мож­но при­менять две основные модели раз­работ­ки: на сто­роне сер­вера и на сто­роне кли­ента (бра­узе­ра).

Рис. 2. Архитектура Vaadin
Рис. 2. Архи­тек­тура Vaadin

Здесь показа­ны основные архи­тек­турные ком­понен­ты веб‑при­ложе­ния, пос­тро­енно­го с исполь­зовани­ем Vaadin.

 

Серверная модель разработки

 

Оптимизировано для производительности

Сер­верная модель раз­работ­ки для Vaadin явля­ется основной и поз­воля­ет соз­давать закон­ченные при­ложе­ния без раз­работ­ки на сто­роне кли­ента. При этом исполь­зует­ся AJAX-дви­жок Vaadin Client-Side Engine, который фор­миру­ет поль­зователь­ский интерфейс в бра­узе­ре.
Сер­верный под­ход поз­воля­ет фак­тичес­ки забыть про то, что раз­работ­ка ведет­ся под веб, и раз­рабаты­вать поль­зователь­ский интерфейс поч­ти как тра­дици­онную Java-прог­рамму с непос­редс­твен­ным дос­тупом к дан­ным и сер­висам на сер­вере. При этом сер­верная часть Vaadin позабо­тит­ся и о фор­мирова­нии поль­зователь­ско­го интерфей­са в бра­узе­ре, и об AJAX-вза­имо­дей­ствии меж­ду бра­узе­ром и сер­вером. Дви­жок Vaadin осу­щест­вля­ет рен­деринг поль­зователь­ско­го интерфей­са при­ложе­ния сер­верной сто­роны в бра­узе­ре и реали­зует все детали обме­на кли­ента и сер­вера.

Сер­верная часть при­ложе­ния Vaadin исполня­ется как обыч­ный сер­влет сер­вера при­ложе­ний Java. Она пред­став­ляет собой чис­тую Java в JAR-фай­ле, который может добав­лять­ся к любому стан­дар­тно­му веб‑при­ложе­нию и работа­ет на любом кон­тей­нере сер­вле­тов или пор­тле­тов от Tomcat до Oracle WebLogic. Сер­влет при­нима­ет HTTP-зап­росы от кли­ента и интер­пре­тиру­ет их как события кон­крет­ной поль­зователь­ской сес­сии. События ассо­цииро­ваны с ком­понен­тами поль­зователь­ско­го интерфей­са и дос­тавля­ются к обра­бот­чикам (event listeners), опре­делен­ным в при­ложе­нии. Если логика поль­зователь­ско­го интерфей­са вно­сит изме­нения в ком­понен­ты поль­зователь­ско­го интерфей­са со сто­роны сер­вера, сер­влет рен­дерит их для отоб­ражения в веб‑бра­узе­ре и фор­миру­ет ответ. Дви­жок кли­ент­ской час­ти, выпол­няемый в бра­узе­ре, получа­ет ответ и на его осно­ве про­изво­дит изме­нения в заг­ружен­ной в бра­узе­ре веб‑стра­нице.

 

Клиентская модель разработки

 

Оптимизировано для контроля

Кли­ент­ская модель поз­воля­ет раз­рабаты­вать вид­жеты и при­ложе­ния на язы­ке Java, которые затем ком­пилиру­ются в выпол­няемый в бра­узе­ре JavaScript с помощью ком­пилято­ра Vaadin Compiler, осно­ван­ного на Google Web Toolkit (GWT). Мож­но исполь­зовать и непос­редс­твен­но JavaScript. Это пре­дос­тавля­ет пол­ный дос­туп к струк­туре DOM и мак­сималь­ный кон­троль над бра­узе­ром.

 

Подготовка среды разработки

Ни­же опи­сыва­ется исполь­зование Vaadin в сре­де NetBeans 8.0.2 (вер­сия Vaadin Plug-in for NetBeans - 1.1.3); во врез­ке есть ссыл­ки на обу­чающие видео для работы в IntelliJ IDEA и Eclipse (пла­гин для Eclipse вклю­чает в себя гра­фичес­кий редак­тор поль­зователь­ско­го интерфей­са).

Пер­вым шагом в NetBeans IDE будет уста­нов­ка пла­гина (Tools → Plugins → Available Plugins, ввес­ти vaadin в поле Search, уста­новить галоч­ку у 'Vaadin Plug-in for NetBeans' и нажать Install, сог­ласив­шись со все­ми воп­росами).

Те­перь при соз­дании нового про­екта (File → New Project) ста­ла дос­тупна новая катего­рия Vaadin. Выберем Vaadin Web Application Project, наж­мем Next и ука­жем имя нового про­екта, нап­ример myvaadin.

Рис. 3. Структура проекта
Рис. 3. Струк­тура про­екта

Пос­ле нажатия Finish будет соз­дана груп­па про­ектов при­ложе­ния Vaadin по умол­чанию. Основной файл с минималь­ным при­мером исходно­го кода при­ложе­ния Vaadin рас­положен в про­екте myvaadin-ui, файл /Source Packages/com.mycompany.myvaadin/MyUI.java; его клю­чевая часть выг­лядит так (опу­щены инс­трук­ции package и import):

@Theme("mytheme")
@Widgetset("com.mycompany.myvaadin.MyAppWidgetset")
public class MyUI extends UI {
@Override
protected void init(VaadinRequest vaadinRequest) {
final VerticalLayout layout = new VerticalLayout();
layout.setMargin(true);
setContent(layout);
Button button = new Button("Click Me");
button.addClickListener(new Button.ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
layout.addComponent(new Label("Thank you for clicking"));
}
});
layout.addComponent(button);
}
@WebServlet(urlPatterns = "/*", name = "MyUIServlet", asyncSupported = true)
@VaadinServletConfiguration(ui = MyUI.class, productionMode = false)
public static class MyUIServlet extends VaadinServlet {
}
}

В этом прос­тей­шем про­екте объ­явля­ется класс MyUI, явля­ющий­ся нас­ледни­ком клас­са UI. В нем пере­опре­деля­ется метод init(). Внут­ри него соз­дает­ся вер­тикаль­ная ком­понов­ка VerticalLayout, у нее вклю­чает­ся отступ (margin), соз­дает­ся новая кноп­ка с обра­бот­чиком нажатия, который добав­ляет ком­понент типа Label с  тек­сто­вой стро­кой. Затем кноп­ка добав­ляет­ся к ком­понов­ке вызовом метода addComponent(). Дирек­тива @Theme("mytheme") зада­ет исполь­зуемую тему офор­мле­ния (о них чуть ниже).

Пе­ред пер­вым запус­ком пересо­берем все про­екты (пра­вый клик на 'myvaadin - myvaadin-parent' → Build)

Для запус­ка в отла­доч­ном режиме мож­но исполь­зовать пла­гин Jetty или интегри­рован­ный в NetBeans сер­вер GlassFish Server
Щел­кнем пра­вой кноп­кой на про­екте → Debug –> в окне Select Deployment Server из выпада­юще­го спис­ка выберем GlassFish Server.

Пос­ле того как будут уста­нов­лены все зависи­мос­ти и переком­пилиро­ваны ком­понен­ты при­ложе­ния, запус­тится сер­влет и авто­мати­чес­ки откро­ется окно бра­узе­ра.

Рис. 4. Минимальное приложение Vaadin
Рис. 4. Минималь­ное при­ложе­ние Vaadin
 

Темы и стили

Взгля­нем непос­редс­твен­но в инспек­торе или в Firebug, что собой пред­став­ляет кноп­ка на нашей фор­ме (рис. 5).

Рис. 5. Кнопка пользовательского интерфейса
Рис. 5. Кноп­ка поль­зователь­ско­го интерфей­са
<div tabindex="0" role="button" class="v-button v-widget">
<span class="v-button-wrap">
<span class="v-button-caption">Click Me</span>
</span>
</div>
Рис. 6. Просмотр HTML и стилей в Firebug
Рис. 6. Прос­мотр HTML и сти­лей в Firebug

Все сти­ли кноп­ки берут­ся из фай­ла styles.css. Этот файл находит­ся в раз­деле /Web Pages/VAADIN/themes/mytheme/ про­екта myvaadin-ui и генери­рует­ся из SASS-фай­лов styles.scss, mytheme.scss и addons.scss, раз­мещен­ных в том же катало­ге. В них за осно­ву берет­ся базовый стиль, называ­ющий­ся Valo (его пре­дыду­щая вер­сия называ­лась Reindeer и мес­тами по‑преж­нему упо­мина­ется в докумен­тации). Почитать про Valo мож­но здесь, а здесь мож­но пос­мотреть при­меры всех ком­понен­тов.

Ос­новные парамет­ры темы вынесе­ны в перемен­ные, и для пол­ной сме­ны внеш­него вида при­ложе­ния дос­таточ­но изме­нения счи­тано­го чис­ла парамет­ров. Нап­ример, цвет шриф­та опре­деля­ется авто­мати­чес­ки на осно­вании цве­та фона. Сам же цвет фона зада­ется перемен­ной $v-background-color. Что­бы изме­нить его, добавим в начало фай­ла mytheme.scss сле­дующую стро­ку:

$v-background-color: #000;

Google Web Toolkit (GWT)

Google Web Toolkit (GWT) — биб­лиоте­ка с откры­тым кодом, пре­дос­тавля­ющая набор Java API и визу­аль­ных ком­понен­тов, поз­воля­ющих раз­рабаты­вать AJAX-при­ложе­ния на Java и затем ком­пилиро­вать их исходные тек­сты в высоко опти­мизи­рован­ный JavaScript, работа­ющий на всех основных бра­узе­рах, вклю­чая мобиль­ные бра­узе­ры для Android и iPhone. Под­робнее — здесь.

Vaadin TouchKit

Vaadin TouchKit пред­назна­чен для раз­работ­ки при­ложе­ний для мобиль­ных устрой­ств. В него вхо­дят ком­понен­ты, опти­мизи­рован­ные для мобиль­ного интерфей­са, а так­же фун­кции, спе­цифич­ные для мобиль­ных устрой­ств. Кро­ме обыч­ного интерфей­са, фор­миру­емо­го на сер­вере, TouchKit под­держи­вает спе­циаль­ный офлайн‑режим, в котором кли­ент­ский интерфейс сох­раня­ется в кеше бра­узе­ра и вклю­чает­ся авто­мати­чес­ки при недос­тупнос­ти сети.

За­тем нуж­но щел­кнуть пра­вой кноп­кой мыш­ки на про­екте myvaadin-ui и выб­рать Vaadin → Compile Widgetset and Theme или Compile Theme, пос­ле чего обно­вить в бра­узе­ре стра­ницу.

При этом фоновый цвет боль­шинс­тва эле­мен­тов про­екта изме­нит­ся на чер­ный, цвет шриф­та изме­нит­ся авто­мати­чес­ки.

Бла­года­ря такому под­ходу для того, что­бы пол­ностью пере­офор­мить при­ложе­ние, ска­жем, под плос­кий стиль Metro, дос­таточ­но двух десят­ков строк, пере­опре­деля­ющих зна­чения перемен­ных, без изме­нения собс­твен­но сти­лей. Резуль­тат мож­но пос­мотреть здесь (выб­рав тему Metro в пра­вом вер­хнем углу), а исходный текст — здесь:

Мож­но пере­опре­делять стиль и нап­рямую. К при­меру, изме­нить цвет над­писи на кноп­ке мож­но, добавив в файл mytheme.scss сле­дующие стро­ки (ниже стро­ки «// Insert your own theme rules here»):

$textcolor: red;
.v-button-caption {
color: $textcolor;
}

Vaadin TestBench

Ба­зиру­ется на биб­лиоте­ке Selenium, что поз­воля­ет управлять бра­узе­ром непос­редс­твен­но из кода Java.
С помощью Vaadin TestBench реали­зует­ся авто­мати­зиро­ван­ное тес­тирова­ние на всех уров­нях и фазах раз­работ­ки вплоть до срав­нения скрин­шотов.
Бо­лее под­робно мож­но про­читать здесь.

Дополнения Vaadin

В Vaadin Directory на дан­ный момент поч­ти 500 раз­личных допол­нений, сре­ди которых мож­но, нап­ример, отме­тить ком­понент Vaadin Charts, пред­назна­чен­ный для отри­сов­ки гра­фиков и диаг­рамм.

Для дос­тупа к катало­гу допол­нений в кон­текс­тном меню про­екта есть пункт Open Add-Ons Browser.

За­тем переком­пилиро­вать темы и обно­вить стра­ницу бра­узе­ра.

Вмес­то того что­бы соз­давать свою тему, мож­но вос­поль­зовать­ся и одной из готовых, изме­нив наз­вание темы с mytheme на одно из сле­дующих: valo, runo, reindeer, chameleon, liferay.

Под­робнее о темах мож­но про­читать здесь.

 

Создание браузерного файл-менеджера

Что­бы почувс­тво­вать всю эле­ган­тность под­хода, пред­лага­емо­го Vaadin, реали­зуем про­тотип файл‑менед­жера.

 

Отображение файловой системы — знакомство с TreeTable и контейнерами

Container — интерфейс Vaadin, пред­став­ляющий собой источник таб­личных или иерар­хичес­ких дан­ных. Под­робнее о кон­тей­нерах мож­но почитать здесь. Для дос­тупа к базе дан­ных SQL пред­назна­чен кон­тей­нер SQLContainer. Для фай­ловой сис­темы так­же сущес­тву­ет готовый кон­тей­нер FilesystemContainer, который мы и исполь­зуем в этом про­екте.

Кон­тей­нер мож­но задавать в качес­тве источни­ка дан­ных для эле­мен­тов типа Table (таб­личные дан­ные), Tree и TreeTable (для отоб­ражения иерар­хичес­ких дан­ных) и дру­гих.

Нач­нем с того, что соз­дадим новый про­ект с наз­вани­ем fileman. Добавим клас­су MyUI метод соз­дания и ини­циали­зации эле­мен­та TreeTable, который будет отоб­ражать струк­туру катало­га (если стро­ка в исходном тек­сте под­све­чива­ется крас­ным, это зна­чит, что для дан­ного клас­са нет соот­ветс­тву­ющей стро­ки import; что­бы ее добавить, мож­но нажать и выб­рать «Add import for...». Нуж­но уточ­нить, что ниже для клас­са File пот­ребу­ется выб­рать из пред­ложен­ных имен­но java.io.File):

public class MyUI extends UI
{
. . .
private TreeTable treetable;
private void initFileTree(ComponentContainer parentLayout) {
// Создадим объект TreeTable для отображения иерархических данных в табличном виде
treetable = new TreeTable("File System");
treetable.setSelectable(true);
treetable.setColumnCollapsingAllowed(true);
treetable.setColumnReorderingAllowed(true);
treetable.setSizeFull();
parentLayout.addComponent(treetable);
}
. . .
}

До­бавим метод уста­нов­ки новых дан­ных TreeTable из FilesystemContainer

private void updateFileTree(File sourcePath) {
// Создаем контейнер файловой системы
FilesystemContainer currentFileSystem = new FilesystemContainer(sourcePath);
currentFileSystem.setRecursive(false); // Отключаем рекурсивное считывание подкаталогов
// Связываем его с объектом TreeTable, отображающим файловую систему
treetable.setContainerDataSource(currentFileSystem);
treetable.setItemIconPropertyId("Icon");
treetable.setVisibleColumns(new Object[]{"Name", "Size", "Last Modified"}); // Для того чтобы скрыть колонку с идентификатором иконки, укажем нужные колонки
}

Так­же добавим метод опре­деле­ния катало­га про­екта по умол­чанию.

private File currentPath;
// Вспомогательная функция для получения каталога приложения по умолчанию
// ~/NetBeansProjects/fileman/target/fileman-1.0-SNAPSHOT/
private void getDefaultDirectory() {
UI ui = MyVaadinUI.getCurrent();
VaadinSession session = ui.getSession();
VaadinService service = session.getService();
currentPath = service.getBaseDirectory();
}

Соз­дадим новый метод initAll, добавив в него вызовы объ­явленных выше методов:

// Инициализация всех элементов
private void initAll(VerticalLayout layout) {
initFileTree(layout);
getDefaultDirectory();
updateFileTree(currentPath);
}

В методе init() уда­лим все, что свя­зано с кноп­кой button, и добавим в кон­це вызов нового метода initAll() так, что­бы init() выг­лядел сле­дующим обра­зом:

@Override
protected void init(VaadinRequest request) {
final VerticalLayout layout = new VerticalLayout();
layout.setMargin(true);
setContent(layout);
initAll(layout);
}

До­бавим заготов­ки под фун­кции для

// Обновление всех элементов
private void updateAll() {
updateFileTree(currentPath);
updateInfo();
}
// Обновление информации о файле/каталоге (при изменении файла/каталога)
private void updateInfo() {
}

Сох­раним файл и запус­тим при­ложе­ние в отла­доч­ном режиме. Если при­ложе­ние уже было запуще­но рань­ше, то пос­ле сох­ранения и завер­шения раз­верты­вания (deploy) дос­таточ­но прос­то обно­вить стра­ницу в бра­узе­ре.

 

Обработка событий компонента TreeTable

В кон­це метода initFileTree добавим обра­бот­чик оди­нар­ного и двой­ного нажатия кно­пок мыши:

// Добавляем обработчик нажатия
treetable.addItemClickListener(new ItemClickEvent.ItemClickListener() {
@Override
public void itemClick(ItemClickEvent itemClickEvent) {
String clickedFilename = itemClickEvent.getItemId().toString(); // Элемент, на котором была нажата кнопка мыши
System.out.println("ItemClick: pathname:" + clickedFilename);
// Если двойной клик
if (itemClickEvent.isDoubleClick()) {
doChangeDir(clickedFilename);
} else {
doSelectFile(clickedFilename);
}
}
});

До­бавим методы клас­су MyUI для обра­бот­ки дей­ствий поль­зовате­ля

private String selectedFilename;
// Пользовательское действие обновление каталога
private void doRefresh() {
updateAll();
}
// Пользовательское действие переход в другой каталог
private void doChangeDir(String path) {
currentPath = new File(path);
if (currentPath.isDirectory()) {
selectedFilename = null;
updateAll();
}
}
// Пользовательское действие переход в каталог на уровень выше
private void doUpLevel() {
currentPath = currentPath.getParentFile();
selectedFilename = null;
updateAll();
}
// Пользовательское действие выбор файла
private void doSelectFile(String filename) {
selectedFilename = filename;
updateInfo();
}

С это­го момен­та по двой­ному кли­ку уже мож­но перехо­дить в каталог, находя­щий­ся уров­нем ниже.

 

Главное меню — компонент MenuBar

До­бавим глав­ное меню с пун­кта­ми Refresh и Up Level в под­меню File, подоб­ное горизон­таль­ным меню тра­дици­онных при­ложе­ний:

private void initMenuBar(Layout parentLayout) {
// Описание объекта MenuBar
// https://vaadin.com/book/-/page/components.menubar.html
// Создаем главное меню
MenuBar menuBar = new MenuBar(); // Создаем объект
menuBar.setWidth("100%"); // Растягиваем на 100% доступной ширины
parentLayout.addComponent(menuBar); // Добавляем в layout
// Добавляем в главное меню подменю File
final MenuItem fileMenuItem = menuBar.addItem("File", null, null);
// Добавляем в меню File элемент Refresh и обработчик при его выборе
fileMenuItem.addItem("Refresh", FontAwesome.REFRESH, new MenuBar.Command() {
@Override
public void menuSelected(MenuItem selectedItem) {
doRefresh();
}
});
// Добавляем в меню File элемент Up Level и обработчик при его выборе
fileMenuItem.addItem("Up Level", FontAwesome.ARROW_UP, new MenuBar.Command() {
@Override
public void menuSelected(MenuItem selectedItem) {
doUpLevel();
}
});
}
private void updateMenuBar() {
// Пока ничего не делать
}

И вызов этих методов в метод InitAll() пер­вой стро­кой (ина­че меню ока­жет­ся ниже всех дру­гих эле­мен­тов):

initMenuBar(layout);

и в updateInfo():

updateMenuBar();
 

Верхняя и нижняя панели

Сле­дующим шагом добавим панель свер­ху, на которой рас­положим кноп­ки и информа­цию о текущем пути / име­ни выб­ранно­го фай­ла и панель сни­зу для отоб­ражения информа­ции о фай­ле. Внут­ри клас­са MyUI добавим:

private Label labelFileName;
// Инициализация верхней панели, содержащей кнопки и текущий путь / выбранный файл
private void initTopPanel(Layout parentLayout) {
// Создаем новую горизонтальную компоновку, которая будет служить панелью инструментов
HorizontalLayout topPanelLayout = new HorizontalLayout();
// Растягиваем на 100% доступной ширины
topPanelLayout.setWidth("100%");
// Между элементами будет пустое пространство
topPanelLayout.setSpacing(true);
// Добавляем к основной компоновке
parentLayout.addComponent(topPanelLayout);
// Создаем кнопку Refresh
// Создаем сам объект
Button button = new Button("Refresh");
// Задаем иконку из FontAwesome
button.setIcon(FontAwesome.REFRESH);
// Есть стили разных размеров
// button.addStyleName(ValoTheme.BUTTON_SMALL);
// Добавляем в компоновку
topPanelLayout.addComponent(button);
// Добавляем обработчик нажатия
button.addClickListener(new Button.ClickListener() {
@Override
public void buttonClick(Button.ClickEvent event) {
doRefresh();
}
});
// Создаем кнопку Up Level
// Создаем сам объект
button = new Button("Up Level");
// Задаем иконку из FontAwesome
button.setIcon(FontAwesome.ARROW_UP);
// Есть стили разных размеров
// button.addStyleName(ValoTheme.BUTTON_SMALL);
// Добавляем в компоновку
topPanelLayout.addComponent(button);
// Добавляем обработчик нажатия
button.addClickListener(new Button.ClickListener() {
@Override
public void buttonClick(Button.ClickEvent event) {
doUpLevel();
}
});
// Добавляем текст с именем выбранного файла
// Создаем сам объект
labelFileName = new Label();
// Добавляем в компоновку
topPanelLayout.addComponent(labelFileName);
topPanelLayout.setComponentAlignment(labelFileName, Alignment.MIDDLE_CENTER);
// Данный компонент будет занимать все доступное место
topPanelLayout.setExpandRatio(labelFileName, 1);
}
// Обновление верхней панели
private void updateTopPanel(File currentPath, String selectedFilename) {
if (selectedFilename != null) {
labelFileName.setValue(selectedFilename);
} else {
labelFileName.setValue(currentPath.toString());
}
}

Ини­циали­зация ниж­ней панели, содер­жащей информа­цию о выб­ранном фай­ле

Label[] bottomLabels;
private void initBottomPanel(Layout parentLayout) {
final String[] captions = new String[]{
"File Size (Bytes)", "File Date", "Usable Space (Bytes)", "Total Space (Bytes)", "Free Space (Bytes)"
};
HorizontalLayout bottomPanelLayout = new HorizontalLayout();
// Растягиваем на 100% доступной ширины
bottomPanelLayout.setWidth("100%");
parentLayout.addComponent(bottomPanelLayout);
// Создаем объекты Label для отображения информации о файле
bottomLabels = new Label[captions.length];
for (int i = 0; i < captions.length; i++) {
bottomLabels[i] = new Label();
bottomLabels[i].setCaption(captions[i]);
bottomLabels[i].setValue("NA");
bottomPanelLayout.addComponent(bottomLabels[i]);
}
}
// Обновление нижней панели
private void updateBottomPanel(String pathname) {
try {
File file = new File(pathname);
// Присваиваем значения объектам Label информация о файле
bottomLabels[0].setValue(Long.toString(file.length()));
bottomLabels[1].setValue((new Date(file.lastModified())).toString());
// Информация о диске
bottomLabels[2].setValue(Long.toString(file.getUsableSpace()));
bottomLabels[3].setValue(Long.toString(file.getTotalSpace()));
bottomLabels[4].setValue(Long.toString(file.getFreeSpace()));
} catch (Exception e) {
// Скроем исключительную ситуацию
for (Label bottomLabel : bottomLabels) {
bottomLabel.setValue("NA");
}
}
}

До­бав­ляем вызов этих методов в метод InitAll(), при­ведя его к сле­дующе­му виду:

private void initAll(VerticalLayout layout) {
initMenuBar(layout);
initTopPanel(layout);
initFileTree(layout);
getDefaultDirectory();
updateFileTree(currentPath);
initBottomPanel(layout);
}

и в updateInfo(), при­ведя его к сле­дующе­му виду:

private void updateInfo() {
updateMenuBar();
updateTopPanel(currentPath, selectedFilename);
updateBottomPanel(selectedFilename);
}

Пос­ле сох­ранения и обновле­ния стра­ницы в бра­узе­ре, файл‑менед­жер получит меню, панель инс­тру­мен­тов и панель ста­туса.

 

Предварительный просмотр и сплиттер — компоненты HorizontalSplitPanel, Embedded

До­бавим нашему файл‑менед­жеру панель пред­варитель­ного прос­мотра гра­фичес­ких фай­лов. По ана­логии нес­ложно сде­лать пред­варитель­ный прос­мотр для тек­сто­вых фай­лов с помощью ком­понен­та TextArea.

private HorizontalLayout previewLayout;
private Embedded previewEmbedded;
// Инициализация основной панели, содержащей просмотр файловой структуры и предварительный просмотр файла
private void initMainPanels(VerticalLayout parentLayout) {
HorizontalSplitPanel mainPanels = new HorizontalSplitPanel();
mainPanels.setSizeFull();
parentLayout.addComponent(mainPanels);
parentLayout.setExpandRatio(mainPanels, 1);
initFileTree(mainPanels);
initPreview(mainPanels);
}
// Инициализация панели предварительного просмотра файла
private void initPreview(ComponentContainer parentLayout) {
previewLayout = new HorizontalLayout();
previewLayout.setSizeFull();
parentLayout.addComponent(previewLayout);
// Создаем элемент для предпросмотра изображений
// Создаем объект Embedded
previewEmbedded = new Embedded("Preview area", null);
// Задаем видимость
previewEmbedded.setVisible(true);
// Добавляем в компоновку
previewLayout.addComponent(previewEmbedded);
// Располагаем по центру
previewLayout.setComponentAlignment(previewEmbedded, Alignment.MIDDLE_CENTER);
}
// Скрыть предварительный просмотр файла
private void clearPreview() {
previewEmbedded.setSource(null);
previewEmbedded.setVisible(true);
}
// Обновить предварительный просмотр файла
private void updatePreview(String pathname) {
if (pathname == null || pathname.length() == 0) {
clearPreview();
return;
}
// Выделим расширение файла
File file = new File(pathname);
int lastIndexOf = pathname.lastIndexOf(".");
String extension = (lastIndexOf == -1) ? "" : pathname.substring(lastIndexOf);
// Ограничение на размер файла для предпросмотра до 128 Кб
final int PREVIEW_FILE_LIMIT = 128 * 1024;
// Расширения файлов для предпросмотра с помощью объекта Embedded (изображения, Flash и так далее)
final String[] imageExtensions = new String[]{
".gif", ".jpeg", ".jpg", ".png", ".bmp", ".ico", ".cur", "swf", "svg"
};
// Скроем объект, используемый для предпросмотра
previewEmbedded.setVisible(false);
// Проверим, не превышает ли размер файла пороговый
if (file.length() > PREVIEW_FILE_LIMIT) {
clearPreview();
return;
}
// Если расширение файла в списке изображений
if (Arrays.asList(imageExtensions).contains(extension)) {
Resource resource = new FileResource(file); // Создаем файловый ресурс
previewEmbedded.setSource(resource); // Задаем источник для объекта Embedded
previewEmbedded.setVisible(true); // Показываем объект
previewLayout.setExpandRatio(previewEmbedded, 1.0f); // Будет занимать все доступное место
}
}

И добав­ляем метод initMainPanels() в метод InitAll(), вмес­то вызов метода ини­циали­зации дерева фай­лов initFileTree(), так как теперь он вызыва­ется из initMainPanels:

private void initAll(VerticalLayout layout) {
initMenuBar(layout);
initTopPanel(layout);
// initFileTree(layout);
initMainPanels(layout);
getDefaultDirectory();
updateFileTree(currentPath);
initBottomPanel(layout);
}

и добав­ляем в updateInfo() стро­ку

updatePreview(selectedFilename);

Не забудь ско­пиро­вать изоб­ражение в пап­ку по умол­чанию (<каталог про­ектов NetBeans (NetBeansProjects)>/fileman/fileman-ui/target/fileman-ui-1.0-SNAPSHOT
).

Ну вот и все, с помощью нашего файл‑менед­жера мож­но переме­щать­ся по фай­ловой сис­теме, прос­матри­вать фай­лы и их свой­ства.

Мы получи­ли кли­ент‑сер­верное при­ложе­ние для бра­узе­ра, не написав ни строч­ки на JavaScript, не зат­ратив вре­мени на реали­зацию AJAX-вза­имо­дей­ствия и вооб­ще не задумав­шись о всех нюан­сах веб‑раз­работ­ки.

Рис. 6. Интерфейс приложения
Рис. 6. Интерфейс при­ложе­ния

www

  • Под­робно начало работы опи­сано в раз­деле Getting Started with Vaadin элек­трон­ной кни­ги Book of Vaadin.
  • От­личным допол­нени­ем кни­ге слу­жит Book of Vaadin Examples, с фраг­мента­ми исходно­го кода для подав­ляюще­го боль­шинс­тва раз­делов кни­ги и эле­мен­тов Vaadin.
  • Не­боль­шой учеб­ный при­мер при­ложе­ния мож­но пос­мотреть здесь.
  • Бо­лее слож­ные де­моп­риложе­ния.
  • До­кумен­тация по API дос­тупна здесь.
 

Заключение

В целом фрей­мворк оставля­ет очень при­ятное впе­чат­ление сво­ей про­думан­ностью и докумен­тирован­ностью, боль­шим количес­твом при­меров с исходны­ми кодами на GitHub. Жур­нал «Хакер» (на дан­ный момент под этим утвер­жде­нием под­писал­ся автор, редак­тор и глав­ред) рекомен­дует тебе его исполь­зование, в том чис­ле внутрь и в неог­раничен­ных количес­твах!

Подписаться
Уведомить о
0 комментариев
Межтекстовые Отзывы
Посмотреть все комментарии