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>