com.github.peholmst.mvp4vaadin.navigation.ui.Breadcrumbs.java Source code

Java tutorial

Introduction

Here is the source code for com.github.peholmst.mvp4vaadin.navigation.ui.Breadcrumbs.java

Source

/*
 * 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());
            }
        }
    }
}