Java tutorial
/* * Copyright (c) 2011 Petter Holmstrm * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.peholmst.mvp4vaadin.navigation.ui; import java.util.HashMap; import java.util.Map; import com.github.peholmst.mvp4vaadin.View; import com.github.peholmst.mvp4vaadin.ViewEvent; import com.github.peholmst.mvp4vaadin.ViewListener; import com.github.peholmst.mvp4vaadin.events.DescriptionChangedViewEvent; import com.github.peholmst.mvp4vaadin.events.DisplayNameChangedViewEvent; import com.github.peholmst.mvp4vaadin.navigation.NavigationController; import com.github.peholmst.mvp4vaadin.navigation.NavigationControllerEvent; import com.github.peholmst.mvp4vaadin.navigation.NavigationControllerListener; import com.github.peholmst.mvp4vaadin.navigation.NavigationRequest; import com.github.peholmst.mvp4vaadin.navigation.NavigationRequestBuilder; import com.github.peholmst.mvp4vaadin.navigation.events.CurrentNavigationControllerViewChangedEvent; import com.vaadin.ui.Alignment; import com.vaadin.ui.Button; import com.vaadin.ui.Component; import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.Label; import com.vaadin.ui.Button.ClickEvent; import com.vaadin.ui.themes.BaseTheme; /** * This class implements a breadcrumb navigation bar that shows all the views * currently in a {@link NavigationController} as links in a row, where the * first view corresponds to the first link, etc: * * <pre> * First view >> Second view >> Third view >> ... * </pre> * <p> * As the views change in the controller, the navigation bar will update itself. * A click on any of the navigation links will request the view controller to * navigate to that particular view. * <p> * Both the links and the separators can be customized by implementing the * {@link ButtonFactory} and {@link SeparatorFactory} interfaces, respectively. * * @see #setController(NavigationController) * * @author Petter Holmstrm * @since 1.0 */ public class Breadcrumbs extends HorizontalLayout implements NavigationControllerListener, ViewListener { private static final long serialVersionUID = 4513495936876605206L; public static final String BREADCRUMB_ELEMENT = "breadcrumb-element"; /** * Factory interface for creating breadcrumb separators. * * @see Breadcrumbs#setSeparatorFactory(SeparatorFactory) * @author Petter Holmstrm * @since 1.0 */ public static interface SeparatorFactory extends java.io.Serializable { /** * Creates and returns a component to be used as a separator between * breadcrumbs. */ Component createSeparator(); } /** * Default implementation of {@link SeparatorFactory}. The separators are * labels containing the "" character and having the * {@link Breadcrumbs#BREADCRUMB_ELEMENT} style. * * @author Petter Holmstrm * @since 1.0 */ public static class DefaultSeparatorFactory implements SeparatorFactory { private static final long serialVersionUID = 7957216244739746986L; @Override public Component createSeparator() { final Label separator = new Label(""); separator.setSizeUndefined(); separator.addStyleName(BREADCRUMB_ELEMENT); return separator; } } /** * Factory interface for creating breadcrumb buttons. * * @see Breadcrumbs#setButtonFactory(ButtonFactory) * @author Petter Holmstrm * @since 1.0 */ public static interface ButtonFactory extends java.io.Serializable { /** * Creates and returns a button for the specified view. The click * listener will be registered by the breadcrumbs component. */ Button createButton(View view); /** * Updates the button texts. This method is called when the display name * and/or the description of the specified view are changed. */ void updateButtonTexts(Button button, View view); } /** * Default implementation of {@link ButtonFactory}. The created buttons have * the {@link BaseTheme#BUTTON_LINK} and * {@link Breadcrumbs#BREADCRUMB_ELEMENT} styles. * * @author Petter Holmstrm * @since 1.0 */ public static class DefaultButtonFactory implements ButtonFactory { private static final long serialVersionUID = 8031407455065485896L; @Override public Button createButton(View view) { final Button btn = new Button(); btn.setStyleName(BaseTheme.BUTTON_LINK); btn.setSizeUndefined(); btn.addStyleName(BREADCRUMB_ELEMENT); updateButtonTexts(btn, view); return btn; } @Override public void updateButtonTexts(Button button, View view) { button.setCaption(view.getDisplayName()); button.setDescription(view.getViewDescription()); } } private SeparatorFactory separatorFactory = new DefaultSeparatorFactory(); private ButtonFactory buttonFactory = new DefaultButtonFactory(); private NavigationController controller; private Map<View, Button> viewButtonMap = new HashMap<View, Button>(); /** * Returns the navigation controller whose view stack will be displayed as * breadcrumbs. If no controller has been set, <code>null</code> is * returned. */ public NavigationController getController() { return controller; } /** * Sets the navigation controller to use. This component will register * itself as a listener of the controller. Setting the controller to * <code>null</code> will unregister the listener. */ public void setController(NavigationController controller) { if (this.controller != null) { this.controller.removeListener(this); } this.controller = controller; addBreadcrumbsForControllerRemovingAnyExistingOnes(); if (this.controller != null) { this.controller.addListener(this); } } /** * Returns the separator factory to use for creating separators between * breadcrumb buttons. */ public SeparatorFactory getSeparatorFactory() { return separatorFactory; } /** * Sets the separator factory to use for creating separators between * breadcrumb buttons. Set this value to <code>null</code> to use the * default separator factory. */ public void setSeparatorFactory(SeparatorFactory separatorFactory) { if (separatorFactory == null) { separatorFactory = new DefaultSeparatorFactory(); } this.separatorFactory = separatorFactory; } /** * Returns the button factory to use for creating breadcrumb buttons. */ public ButtonFactory getButtonFactory() { return buttonFactory; } /** * Sets the button factory to use for creating breadcrumb buttons. Set this * value to <code>null</code> to use the default button factory. */ public void setButtonFactory(ButtonFactory buttonFactory) { if (buttonFactory == null) { buttonFactory = new DefaultButtonFactory(); } this.buttonFactory = buttonFactory; } private void addBreadcrumbsForControllerRemovingAnyExistingOnes() { removeBreadcrumbs(); if (getController() != null) { for (View view : getController().getViewStack()) { addBreadcrumbForView(view); } } } @Override public void handleNavigationControllerEvent(NavigationControllerEvent event) { if (event.getSource() != getController() || !(event instanceof CurrentNavigationControllerViewChangedEvent)) { return; } // TODO Maybe this method should be optimized so that not all buttons // need to be removed unless absolutely necessary. addBreadcrumbsForControllerRemovingAnyExistingOnes(); } protected void addBreadcrumbForView(final View view) { addSeparatorForView(view); final Button btn = getButtonFactory().createButton(view); final NavigationRequest navigationRequest = NavigationRequestBuilder.newInstance() .startWithPathToView(getController(), view).buildRequest(); btn.addListener(new Button.ClickListener() { private static final long serialVersionUID = 5199653237630939848L; @Override public void buttonClick(ClickEvent event) { getController().navigate(navigationRequest); } }); viewButtonMap.put(view, btn); view.addListener(this); addComponent(btn); setComponentAlignment(btn, Alignment.MIDDLE_LEFT); } protected void addSeparatorForView(final View view) { if (getController().containsMoreThanOneElement()) { Component separator = getSeparatorFactory().createSeparator(); addComponent(separator); setComponentAlignment(separator, Alignment.MIDDLE_LEFT); } } protected void removeBreadcrumbs() { removeAllComponents(); for (View view : viewButtonMap.keySet()) { view.removeListener(this); } viewButtonMap.clear(); } @Override public void handleViewEvent(ViewEvent event) { if (event instanceof DisplayNameChangedViewEvent || event instanceof DescriptionChangedViewEvent) { final Button btn = viewButtonMap.get(event.getSource()); if (btn != null) { getButtonFactory().updateButtonTexts(btn, event.getSource()); } } } }