Все вы, наверное, слышали о Twitter Bootstrap , потрясающем наборе компонентов, позволяющем создавать красивые веб-страницы и приложения, не углубляясь в хаки CSS / JS. Если вы хотите узнать, на что способна эта библиотека, посетите http://builtwithbootstrap.com/ и проверьте, как люди используют ее для создания своих веб-сайтов.
Но этот пост не должен касаться самого Bootstrap. В нескольких следующих параграфах я расскажу, как я создал повторно используемый компонент Wicket, содержащий меню верхней панели навигации Bootstrap. И конечный результат с использованием Wicket в нашем примере веб-приложения будет выглядеть следующим образом.
Перед укладкой:
После:
И что самое важное, готовый компонент может использоваться без каких-либо изменений в HTML-файлах, все определяется с использованием чистой Java:
add(new TwitterBootstrapNavBarPanel.Builder("navBar", HomePage.class, "Example Web App", getActiveMenu()) .withMenuItem(MenuItemEnum.CLIENTS, ClientsPage.class) .withMenuItemAsDropdown(MenuItemEnum.PRODUCTS, ProductOnePage.class, "Product One") .withMenuItemAsDropdown(MenuItemEnum.PRODUCTS, ProductTwoPage.class, "Product Two") .withMenuItemAsDropdown(MenuItemEnum.PRODUCTS, ProductTwoPage.class, "Product Three") .withMenuItemAsDropdown(MenuItemEnum.ABOUT_US, TeamPage.class, "Team") .withMenuItemAsDropdown(MenuItemEnum.ABOUT_US, OurSkillsPage.class, "Our Skills") .withMenuItem(MenuItemEnum.CONTACT, ContactPage.class) .build());
Если вы парень типа «покажи мне код», перейдите прямо к моему проекту на Github, в котором содержится рабочий пример веб-приложения с этой панелью навигации. Если вы хотите прочитать объяснение, как это работает, пожалуйста, продолжайте читать.
Шаг 1: Некоторые приготовления
Компонент NavBar позволяет отображать активный пункт меню по-другому, чтобы пользователь мог определить, где он находится. Поэтому в нашем веб-приложении каждая веб-страница должна сообщать, к какому пункту меню она принадлежит, чтобы ее можно было соответствующим образом выделить.
Итак, в нашем классе BasePage мы добавляем метод:
public abstract MenuItemEnum getActiveMenu();
возвращение определенного перечисления:
public enum MenuItemEnum { CLIENTS("Clients"), PRODUCTS("Products"), ABOUT_US("About us"), CONTACT("Contact"), NONE(""); private String label; private MenuItemEnum(String label) { this.label = label; } // ... }
одно значение используется домашней страницей, поскольку оно не принадлежит ни одному конкретному пункту меню. Другие страницы (страницы продукта, клиент и т. Д.) Возвращают одно из других перечисленных выше значений.
Шаг 2: Разделение разметки на куски многократного использования
Чистая разметка Bootstrap NanBar может выглядеть так:
И если вы посмотрите достаточно внимательно, вы заметите, что его можно разделить на более мелкие, многократно используемые и независимые элементы:
- каждый из этих трех <li> внутри красного прямоугольника представляет один элемент меню и практически одинаков; только активный элемент меню имеет дополнительный параметр класса.
- <li> внутри желтого прямоугольника — это элемент раскрывающегося меню, который содержит метку, некоторые стили и два элемента, которые совпадают с отдельными пунктами меню, описанными выше.
В общем, у нас должно быть три класса:
- Класс панели, представляющий весь NavBar, который будет нашим TwitterBootstrapNavBarPanel
- Класс панели для одного элемента меню <li> — MenuLinkItem
- Класс панели для одного раскрывающегося меню <li> — MenuDropdownItem, который также будет содержать несколько элементов MenuLinkItem для представления элементов в этом раскрывающемся меню.
Итак, начнем с самого простого.
Шаг 3. Один элемент элемента меню — MenuLinkItem
Разметка этого простого элемента действительно несложная, всего одна ссылка.
<wicket:panel> <a href="#" wicket:id="link"/> </wicket:panel>
Его класс Java небольшой и его легко понять:
public class MenuLinkItem extends Panel { public MenuLinkItem(String id, BookmarkablePageLink pageLink, boolean shouldBeActive) { super(id); add(pageLink); if (shouldBeActive) { add(new AttributeAppender("class", " active ")); } } }
То, что мы видим выше, это простая панель, принимающая три параметра, стандартный идентификатор элемента Wicket, ссылку на страницу для размещения в этом пункте меню и индикатор, указывающий, должен ли этот элемент отображаться как активный. Это достигается путем добавления активного имени класса к элементу класса CSS.
Шаг 4: Элемент выпадающего меню — MenuDropdownItem
Теперь становится немного сложнее, так как мы хотим создать панель для нашего пункта меню, содержащую выпадающий список. Его разметка:
<wicket:panel> <li class="dropdown" wicket:id="itemContainer"> <a href="#" class="dropdown-toggle" data-toggle="dropdown"> <span wicket:id="label"/> <b class="caret"></b> </a> <ul class="dropdown-menu"> <li wicket:id="itemLinks"></li> </ul> </li> </wicket:panel>
Итак, что мы имеем здесь:
- Метка калитки (id = метка ) для отображения в виде… метки этого выпадающего элемента
- элемент, содержащий список элементов <li>, представленных Wicket id = itemLinks
- и, наконец, компонент, обертывающий все вышеперечисленное: id = itemContainer
И класс Java для этой панели:
public class MenuDropdownItem extends Panel { public MenuDropdownItem(String id, MenuItemEnum currentMenuItem, Collection> linksInMenuItem, boolean shouldBeActive) { super(id); WebMarkupContainer itemContainer = new WebMarkupContainer("itemContainer"); // (1) if (shouldBeActive) { itemContainer.add(new AttributeAppender("class", " active ")); // (2) } itemContainer.add(new Label("label", currentMenuItem.getLabel())); // (3) RepeatingView repeatingView = new RepeatingView("itemLinks"); // (4) for (BookmarkablePageLink link : linksInMenuItem) { // (5) MenuLinkItem menuLinkItem = new MenuLinkItem(repeatingView.newChildId(), link, false); repeatingView.add(menuLinkItem); } itemContainer.add(repeatingView); add(itemContainer); } }
Вы можете подумать , что это очень сложно. Но расслабьтесь, позвольте мне объяснить это шаг за шагом:
- Здесь мы создаем контейнер для добавления метки и ссылок. Контейнер необходим, потому что иногда мы хотели бы добавить сюда активный стиль, поэтому он должен быть компонентом Wicket.
- Если этот элемент должен быть активным, добавьте правильный стиль.
- Добавьте элемент метки.
- Чтобы отобразить все ссылки на элементы в нашем выпадающем меню, нам нужен ретранслятор. Wicket предоставляет множество компонентов повторителей, но почему мы используем RepeatingView? Потому что эта не производит никакой дополнительной разметки и отображает только внутри разметки, где она расположена (более подробно в javadoc ). Так что в нашем случае он отображает несколько элементов <li> .. </ li> без какого-либо дополнительного html, что хорошо для поддержания всего в соответствии со стилем Twitter Bootstrap.
- Для каждой ссылки, передаваемой нашему выпадающему элементу, мы создаем новый MenuLinkItem (вы помните, когда я подчеркивал, что выпадающий список будет содержать список элементов элемента меню?)
И все, этот компонент теперь понятен, не так ли?
Шаг 5: Объединяем все вместе
Итак, у нас есть два готовых компонента для использования в нашей главной панели. Теперь пришло время представить класс панели и разметку — TwitterBootstrapNavBarPanel. Разметка, к счастью, не сложная:
<div class="navbar navbar-fixed-top"> <div class="navbar-inner"> <div class="container">// (1) <div class="nav-collapse"> <ul class="nav"> <ul class="nav"> <li>// (2)</li> </ul> </ul> </div> </div> </div> </div>
- Это ссылка с ярлыком для перенаправления пользователя на домашнюю страницу нашего приложения.
- Это повторитель для отображения всех элементов меню (простых и выпадающих)
Хотя разметка не сложна для понимания даже на первый взгляд, класс Java отличается и требует более подробного объяснения. Поскольку я хотел, чтобы процесс создания NavBar был максимально плавным, я использовал свободный интерфейс с шаблоном Builder. Итак, во-первых, давайте сосредоточимся на этом элементе:
public static class Builder implements Serializable { private String id; private Class homePage; private String applicationName; private MenuItemEnum activeMenuItem; private Multimap> linksMap = LinkedHashMultimap.create(); // (3) public Builder(String id, Class homePage, String applicationName, // (1) MenuItemEnum activeMenuItem) { this.id = id; this.homePage = homePage; this.applicationName = applicationName; this.activeMenuItem = activeMenuItem; } public Builder withMenuItem(MenuItemEnum menuItem, Class pageToLink) { // (2) Preconditions.checkState(linksMap.containsKey(menuItem) == false, "Builder already contains " + menuItem + ". Please use withMenuItemInDropdown if you need many links in one menu item"); // (4) BookmarkablePageLink link = new BookmarkablePageLink("link", pageToLink); link.setBody(new Model(menuItem.getLabel())); // (5) linksMap.put(menuItem, link); // (6) return this; } // ... }
So what’s happening here:
- Our panel needs three mandatory things: Wicket component id, application name to render a label and home page class to create link so those trhee things will go to the Builder constructor
- This method is responsible for collecting data for simple menu item so we need a menu item enum and class of the page that we want link to in the navigation bar
- To gather all the data we use Google Guava Multimap with MeniItemEnum as a key and a Bookmarkable Links as a values. Multimap is a kind of map which allows multiple values for a single key.
- But for a simple menu item we don’t want to have a more than one menu item duplicated in the navigation bar so we prevent it with a Preconditions check.
- For a simple item we want to have a label equal to MenuItemEnum label so we add body to the link.
- And put link in our multimap.
Now let me explain two last methods in the Builder class:
public Builder withMenuItemAsDropdown(MenuItemEnum menuItem, Class pageToLink, String label) { // (1) BookmarkablePageLink link = new BookmarkablePageLink("link", pageToLink); link.setBody(new Model(label)); linksMap.put(menuItem, link); return this; } public TwitterBootstrapNavBarPanel build() { (2) return new TwitterBootstrapNavBarPanel(this); }
- This method is used to add elements to the dropdown menu item. Here we allow to multimple values for the same menu item (as we will render them as a dropdown under single label). Additionally in the params list we need a label to each link as enum label will be used to render menu item itself and not a links inside it.
- And when we’re ready we execute build() that calls private panel constructor.
Step 6: Panel class
We have everything set up, Builder has all the data we need to create our panel so let’s go to its constructor.
private TwitterBootstrapNavBarPanel(final Builder builder) { super(builder.id); BookmarkablePageLink homePageLink = new BookmarkablePageLink("homePageLink", builder.homePage); // (1) homePageLink.add(new Label("label", builder.applicationName)); // (2) add(homePageLink); RepeatingView repeatingView = new RepeatingView("menuItems"); // (3) for (MenuItemEnum item : builder.linksMap.keySet()) { // (4) boolean shouldBeActive = item.equals(builder.activeMenuItem); Collection> linksInThisMenuItem = builder.linksMap.get(item); // (5) if (linksInThisMenuItem.size() == 1) { //(6) BookmarkablePageLink pageLink = Iterables.get(linksInThisMenuItem, 0); MenuLinkItem menuLinkItem = new MenuLinkItem(repeatingView.newChildId(), pageLink, shouldBeActive); repeatingView.add(menuLinkItem); } else { // (7) repeatingView.add(new MenuDropdownItem(repeatingView.newChildId(), item, linksInThisMenuItem, shouldBeActive)); } } add(repeatingView); }
- Create link to the home page
- Set its label
- Create empty repeater
- For each key from Multimap …
- Get collection of links to place in current menu item
- If there is only one link, add simple menu item (MenuLinkItem element)
- Otherwsie, create MenuDropdownItem with collection of links
Summary
And that’s all. We went through the complete process of building reusable Wicket component based on the top of existing Twitter Bootstrap UI framework. And now it is easy to reuse it in a few next web applications we will create with Wicket.
Of course styling (colors, fonts, etc.) might be different but when we create an internal application for an insurance company or for a bank, they don’t expect UI to be really, really, really awesome. It just should look nice and that’s all. If you have such requirements, Twitter Bootstrap is the tool to choose. And when you encapsulate it with your Java web framework of choice you will get ready to use element that just works.