Własne układy w Vaadin
Krótka przerwa od Guavy i przesiadka na Vaadin. Dziś o własnych szablonach układów.
Problem wyglądu
Często gęsto jest tak, że chcąc uzyskać jakiś konkretny układ graficzny dla bardziej skomplikowanych komponentów graficznych tworzymy własne komponenty. Własne komponenty składamy w bardziej skomplikowane by finalnie uzyskać pożądany efekt.
Doskonałym przykładem tego typu sytuacji są wszystkie aplikacje zarówno webowe jak i desktopowe. Sam też niejednokrotnie na forum 4programmers pisałem, że „jak nie potrafisz ułożyć skomplikowanego layout podziel go na mniejsze i spróbuj znowu”. Jest to oczywiście kolejne zastosowanie metody „dziel i zwyciężaj” lecz w przypadku Vaadin niesie ona duże niebezpieczeństwo.
Problem spotykany jest też w Swingu – spadek wydajności przy renderowaniu okien. Rzecz oczywiście rozbija się o to jak wiele dodatkowej pracy związanej z wyliczaniem pozycji komponentów ma aplikacja. Co więcej, jest to robione „pod spodem” i tym samym ma magiczną zdolność do bycia ignorowanym.
W przypadku Vaadin niepisana zasada mówi, żeby nie zagnieżdżać więcej niż trzy LayoutManagery. Pierwszym będzie zatem główny układ aplikacji. Drugim układ okna, trzecim układ komponentu… i zaczyna być ciasno w przypadku skomplikowanych komponentów.
Jak to ogarnąć?
Paradoksalnie najprościej ten problem rozwiązać stosując stary dobry CSS w połączeniu z klasami rozszerzającymi CustomLayout. Layout ten to nic innego jak swoisty „trik” pozwalający na wykorzystanie szablonów napisanych w czystym html-u jako layoutów w Vaadin.
Część HTML + CSS
W trakcie generowania aplikacji za pomocą mavena możemy podać nazwę wykorzystywanej skórki (theme). Podajemy coś tam, coś tam i teraz w src/main/webapp/VAADIN/themes/ mamy katalog o nazwie jaką podaliśmy. W nim są obecnie tylko pliki css i scss, ze stylami naszej aplikacji. Tworzymy zatem tu katalog layouts, a w nim plik MyCustomLayout.html.
Listing 1. Plik MyCustomLayout.html
<html>
<head>
<title></title>
</head>
<body>
<nav location="button" id="button-box"></nav>
<div location="label" id="label-box"></div>
</body>
</html>
Jak widać jest to zwykły, „goły” plik html. Jedyna różnica w stosunku do innych plików tego typu polega na dodatkowych atrybucie location w elementach, które mają być miejscami „wstrzyknięć” komponentów Vaadin. Następnie w pliku styles.css konfigurujemy nasz układ:
Listing 2. konfiguracja układu w styles.css
#button-box{
border: 1px solid #afafff;
height: 4em;
width: 12em;
}
#label-box{
border: 1px solid #afafff;
height: 4em;
width: 12em;
font-family: garamond;
}
Tyle. Oczywiście można to dowolnie rozbudowywać, przekształcać i dostosowywać do potrzeb. Co więcej jeżeli zastosujemy taki układ jako główny dla aplikacji możemy dodać statyczne grafiki tak jak w HTMLu.
Część javowa
Tu sprawa troszkę się komplikuje jeżeli chcemy sobie ułatwić życie w przyszłości. Po pierwsze musimy przygotować klasę reprezentującą nasz nowy układ:
Listing 3. Klasa MyCustomLayout
public class MyCustomLayout extends CustomLayout {
private final static String NAME = "MyCustomLayout";
public MyCustomLayout() {
super(NAME);
}
public void addComponent(Component c, Placeholders p) {
addComponent(c, p.placeholder);
}
//...
}
Pole NAME po przekazaniu do konstruktora klasy nadrzędnej będzie traktowane jako nazwa pliku przy czym nie należy dodawać sufiksu .html, bo się zjebie.
Kolejnym elementem jest enum Placeholders:
Listing 4. Enum Placeholders
public class MyCustomLayout extends CustomLayout {
//...
public static enum Placeholders {
BUTTON("button"),
LABEL("label");
private final String placeholder;
Placeholders(String label) {
this.placeholder = label;
}
}
}
Który będzie sobie siedział w naszym layoucie. Dlaczego tak? Ponieważ możemy popełnić literówkę w nazwie punku „wstrzyknięcia”. Łatwiej poprawić ją w jednym miejscu niż w wielu.
La grande finale
Czas to wszystko złożyć do kupy w postaci naszej aplikacji testowej. Przerobiłem po prostu wygenerowany kod na taki:
Listing 5. Aplikacja testowa
@Theme("mytheme")
@SuppressWarnings("serial")
public class MyVaadinUI extends UI {
@Override
protected void init(VaadinRequest request) {
final MyCustomLayout layout = new MyCustomLayout();
setContent(layout);
Button button = new Button("Click Me");
button.addClickListener(new Button.ClickListener() {
public void buttonClick(ClickEvent event) {
layout.addComponent(new Label("Thank you for clicking"), LABEL);
}
});
layout.addComponent(button, BUTTON);
}
@WebServlet(value = "/*", asyncSupported = true)
@VaadinServletConfiguration(productionMode = false, ui = MyVaadinUI.class)
public static class Servlet extends VaadinServlet {
}
}
Śmiga całkiem miło.
</body></html>