├── .settings ├── org.eclipse.wst.jsdt.ui.superType.name ├── org.eclipse.wst.jsdt.ui.superType.container ├── org.eclipse.m2e.core.prefs ├── org.eclipse.core.resources.prefs ├── com.vaadin.integration.eclipse.prefs ├── org.eclipse.wst.common.project.facet.core.xml ├── org.eclipse.wst.common.component ├── .jsdtscope └── org.eclipse.jdt.core.prefs ├── .gitignore ├── src └── main │ ├── test │ └── test │ │ ├── ExampleEvent2.java │ │ ├── ExampleEvent1.java │ │ ├── ExampleHandler1.java │ │ └── EventBusTest.java │ ├── webapp │ ├── VAADIN │ │ └── themes │ │ │ ├── img │ │ │ ├── hc_yarnlett_global.png │ │ │ ├── testDiv.html │ │ │ └── test.html │ │ │ └── styles.css │ ├── META-INF │ │ └── MANIFEST.MF │ └── WEB-INF │ │ └── web.xml │ └── java │ └── com │ └── mvplite │ ├── view │ ├── View.java │ ├── NavigationControllerListener.java │ ├── ui │ │ ├── ArrowSeparatorFactory.java │ │ ├── ArrowBreadcrumbElementFactory.java │ │ └── Breadcrumbs.java │ ├── NavigateableView.java │ ├── NavigateableSubView.java │ ├── NavigationController.java │ └── LiteNavigationController.java │ ├── event │ ├── Event.java │ ├── Show404ViewEvent.java │ ├── EventHandler.java │ ├── GlobalEventBusDispatcher.java │ ├── ShowViewEvent.java │ ├── RefresherGlobalEventBusDispatcher.java │ ├── GlobalEventBus.java │ └── EventBus.java │ └── presenter │ └── Presenter.java ├── target ├── maven-archiver │ └── pom.properties └── surefire-reports │ ├── test.EventBusTest.txt │ └── TEST-test.EventBusTest.xml ├── WebContent └── META-INF │ └── MANIFEST.MF ├── .project ├── .classpath ├── README.md └── pom.xml /.settings/org.eclipse.wst.jsdt.ui.superType.name: -------------------------------------------------------------------------------- 1 | Window -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Package Files # 4 | *.jar 5 | *.war 6 | *.ear 7 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.jsdt.ui.superType.container: -------------------------------------------------------------------------------- 1 | org.eclipse.wst.jsdt.launching.baseBrowserLibrary -------------------------------------------------------------------------------- /.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=true 4 | version=1 5 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding//src/main/java=UTF-8 3 | encoding//src/main/test=UTF-8 4 | -------------------------------------------------------------------------------- /src/main/test/test/ExampleEvent2.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import com.mvplite.event.Event; 4 | 5 | public class ExampleEvent2 extends Event{ 6 | 7 | } 8 | -------------------------------------------------------------------------------- /target/maven-archiver/pom.properties: -------------------------------------------------------------------------------- 1 | #Generated by Maven 2 | #Sun Apr 21 19:49:43 CEST 2013 3 | version=2.0 4 | groupId=com.mvplite 5 | artifactId=mvp-lite 6 | -------------------------------------------------------------------------------- /src/main/webapp/VAADIN/themes/img/hc_yarnlett_global.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockeqwe/Vaadin-MVP-Lite/HEAD/src/main/webapp/VAADIN/themes/img/hc_yarnlett_global.png -------------------------------------------------------------------------------- /WebContent/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Implementation-Title: MVP-Lite 3 | Implementation-Version: 2.0.0 4 | Vaadin-Package-Version: 1 5 | Class-Path: 6 | 7 | -------------------------------------------------------------------------------- /src/main/webapp/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Implementation-Title: MVP-Lite 3 | Implementation-Version: 1.0 4 | Vaadin-Package-Version: 1 5 | Class-Path: 6 | 7 | -------------------------------------------------------------------------------- /.settings/com.vaadin.integration.eclipse.prefs: -------------------------------------------------------------------------------- 1 | com.vaadin.integration.eclipse.useLatestNightly=false 2 | com.vaadin.integration.eclipse.widgetsetDirty=false 3 | com.vaadin.integration.eclipse.widgetsetStyle=OBF 4 | eclipse.preferences.version=1 5 | -------------------------------------------------------------------------------- /target/surefire-reports/test.EventBusTest.txt: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------- 2 | Test set: test.EventBusTest 3 | ------------------------------------------------------------------------------- 4 | Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.058 sec 5 | -------------------------------------------------------------------------------- /src/main/java/com/mvplite/view/View.java: -------------------------------------------------------------------------------- 1 | package com.mvplite.view; 2 | 3 | /** 4 | * This is the base interface for any view (like {@link NavigateableView}) 5 | * that can be used within this model-view-presenter framework 6 | * @author Hannes Dorfmann 7 | * 8 | */ 9 | public interface View { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/mvplite/event/Event.java: -------------------------------------------------------------------------------- 1 | package com.mvplite.event; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * This is the super base type for every event, that can be fired to the {@link EventBus} 7 | * @author Hannes Dorfmann 8 | */ 9 | public class Event implements Serializable { 10 | 11 | /** 12 | * 13 | */ 14 | private static final long serialVersionUID = -2678661979380772303L; 15 | 16 | 17 | } 18 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.common.project.facet.core.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/java/com/mvplite/event/Show404ViewEvent.java: -------------------------------------------------------------------------------- 1 | package com.mvplite.event; 2 | 3 | import com.mvplite.view.NavigationController; 4 | 5 | /** 6 | * This event is fired by the {@link NavigationController}, 7 | * (if {@link NavigationController#} 8 | * @author Hannes Dorfmann 9 | * 10 | */ 11 | public class Show404ViewEvent extends ShowViewEvent{ 12 | 13 | private static final long serialVersionUID = -5180315094279640893L; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/test/test/ExampleEvent1.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import com.mvplite.event.Event; 4 | 5 | public class ExampleEvent1 extends Event { 6 | 7 | private static final long serialVersionUID = 5199602648963300772L; 8 | 9 | public ExampleEvent1(String val1, int val2){ 10 | this.val1 = val1; 11 | this.val2 = val2; 12 | } 13 | 14 | public String val1; 15 | public int val2; 16 | 17 | 18 | public String toString(){ 19 | return super.toString()+" "+val1+" "+val2; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.common.component: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/java/com/mvplite/view/NavigationControllerListener.java: -------------------------------------------------------------------------------- 1 | package com.mvplite.view; 2 | 3 | import com.mvplite.view.ui.Breadcrumbs; 4 | 5 | /** 6 | * This Listener is used by ui components (i.e. {@link Breadcrumbs}) to 7 | * listen to a {@link NavigationController} of any navigation changes to update the gui 8 | * @author Hannes Dorfmann 9 | * 10 | */ 11 | public interface NavigationControllerListener { 12 | 13 | /** 14 | * The current view 15 | * @param view 16 | */ 17 | public void onNavigatedTo(NavigateableView view); 18 | } -------------------------------------------------------------------------------- /.settings/.jsdtscope: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/java/com/mvplite/view/ui/ArrowSeparatorFactory.java: -------------------------------------------------------------------------------- 1 | package com.mvplite.view.ui; 2 | 3 | import com.mvplite.view.ui.Breadcrumbs.SeparatorFactory; 4 | import com.vaadin.ui.Component; 5 | 6 | /** 7 | * Is used in combination with the {@link ArrowBreadcrumbElementFactory} 8 | * @author Hannes Dorfmann 9 | * 10 | */ 11 | public class ArrowSeparatorFactory implements SeparatorFactory{ 12 | 13 | private static final long serialVersionUID = -560087683654931093L; 14 | 15 | public Component createSeparator() { 16 | return null; 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/main/test/test/ExampleHandler1.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import com.mvplite.event.EventHandler; 4 | 5 | public class ExampleHandler1 { 6 | 7 | 8 | @EventHandler 9 | public void onTestEvent1(ExampleEvent1 e){ 10 | System.out.println("Received 1 "+e+" "+this); 11 | } 12 | 13 | @EventHandler 14 | public void testit(ExampleEvent1 e){ 15 | System.out.println("Received 2 "+e+" "+this); 16 | } 17 | 18 | @EventHandler 19 | public void onTest2(ExampleEvent2 e){ 20 | System.out.println("on TestEvent2 "+this); 21 | } 22 | 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/mvplite/event/EventHandler.java: -------------------------------------------------------------------------------- 1 | package com.mvplite.event; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | 7 | /** 8 | * This is the base type for every {@link EventHandler} that handles {@link Event}s. 9 | * A {@link EventHandler} must be registered to the {@link EventBus} to receive {@link Event}s. 10 | * This is done by {@link EventBus#addHandler(Object)} 11 | * @author Hannes Dorfmann 12 | * 13 | */ 14 | @Retention(value=RetentionPolicy.RUNTIME) 15 | public @interface EventHandler { 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/mvplite/event/GlobalEventBusDispatcher.java: -------------------------------------------------------------------------------- 1 | package com.mvplite.event; 2 | 3 | /** 4 | * This is the common interface for Dispatchers, that dispatches 5 | * the Event from the {@link GlobalEventBus} to the local {@link EventBus} 6 | * @author Hannes Dorfmann 7 | * 8 | */ 9 | public interface GlobalEventBusDispatcher { 10 | 11 | /** 12 | * Starts to listen to the {@link GlobalEventBus} 13 | * for new incomming {@link Event}s 14 | */ 15 | public void start(); 16 | 17 | /** 18 | * Stops to listen to the {@link GlobalEventBus} 19 | */ 20 | public void stop(); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | Vaadin-MVP-Lite 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.m2e.core.maven2Builder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.m2e.core.maven2Nature 22 | 23 | 24 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 5 | org.eclipse.jdt.core.compiler.compliance=1.7 6 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 7 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 8 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 9 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 10 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 11 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 12 | org.eclipse.jdt.core.compiler.source=1.7 13 | -------------------------------------------------------------------------------- /src/main/java/com/mvplite/view/NavigateableView.java: -------------------------------------------------------------------------------- 1 | package com.mvplite.view; 2 | 3 | import com.mvplite.event.ShowViewEvent; 4 | 5 | /** 6 | * A {@link NavigateableView} is a {@link View} that is used in combination with an {@link NavigationController}. 7 | * @author Hannes Dorfmann 8 | * 9 | */ 10 | public interface NavigateableView extends View { 11 | 12 | /** 13 | * The piece after the # (hash) in the url 14 | * @return 15 | */ 16 | public String getUriFragment(); 17 | 18 | 19 | public String getBreadcrumbTitle(); 20 | 21 | 22 | /** 23 | * This method is called by the {@link LiteNavigationController} to get the Event, that is needed to re 24 | * @return 25 | */ 26 | public ShowViewEvent getEventToShowThisView(); 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/mvplite/view/NavigateableSubView.java: -------------------------------------------------------------------------------- 1 | package com.mvplite.view; 2 | 3 | /** 4 | * The {@link NavigateableSubView} is a sub view of a {@link NavigateableView}. 5 | * That means, that this view has a parent {@link NavigateableView} and you 6 | * can implement a tree of views, since a {@link NavigateableSubView} can also be 7 | * a parent of another {@link NavigateableSubView} 8 | * @author Hannes Dorfmann 9 | * 10 | */ 11 | public interface NavigateableSubView extends NavigateableView { 12 | 13 | 14 | /** 15 | * Get the parent of this {@link NavigateableSubView}. 16 | * Note that a {@link NavigateableSubView} can be a parent of a {@link NavigateableSubView} 17 | * @return The parent {@link NavigateableView} 18 | */ 19 | public NavigateableView getParentView(); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/webapp/VAADIN/themes/styles.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | @import url(../reindeer/styles.css); 3 | 4 | .breadcrumbs { 5 | border-top: 1px solid #ccc; 6 | border-bottom: 1px solid #999; 7 | background: -moz-linear-gradient(top, #fff, #ccc); 8 | background: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#ccc)); 9 | line-height: 24px; 10 | padding-left: 18px; 11 | } 12 | 13 | .breadcrumbs .breadcrumb-element { 14 | float: left; 15 | margin-right: 5px; 16 | display: inline-block; 17 | } 18 | 19 | .breadcrumbs .breadcrumb-element.v-label { 20 | line-height: 24px; 21 | } 22 | 23 | 24 | .breadcrumbs.niceArrowElement { 25 | background-image: url(img/hc_yarnlett_global.png); 26 | background-repeat: no-repeat; 27 | background-position: 100% 0; 28 | } 29 | 30 | 31 | .breadcrumbs.niceArrowElement:hover { 32 | 33 | background-position: 100% -48px; 34 | color: #333; 35 | 36 | } -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/main/test/test/EventBusTest.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import org.junit.BeforeClass; 4 | import org.junit.Test; 5 | 6 | import com.mvplite.event.EventBus; 7 | import com.mvplite.event.EventHandler; 8 | import com.mvplite.event.ShowViewEvent; 9 | 10 | public class EventBusTest { 11 | 12 | private static EventBus eventBus; 13 | 14 | @BeforeClass 15 | public static void setUpBeforeClass() { 16 | eventBus = new EventBus(); 17 | } 18 | 19 | 20 | public static void main(String args[]){ 21 | setUpBeforeClass(); 22 | new EventBusTest().test(); 23 | } 24 | 25 | @Test 26 | public void test() { 27 | 28 | ExampleHandler1 h1 = new ExampleHandler1(); 29 | ExampleHandler1 h2 = new ExampleHandler1(); 30 | eventBus.addHandler(h1); 31 | eventBus.addHandler(h2); 32 | eventBus.addHandler(this); 33 | System.out.println(eventBus.fireEvent(new ExampleEvent1("First", 42))); 34 | System.out.println(eventBus.fireEvent(new ExampleEvent2())); 35 | System.out.println(eventBus.fireEvent(new ShowViewEvent())); 36 | System.out.println(eventBus.fireEvent(new ExampleEvent1("Second", 23))); 37 | 38 | } 39 | 40 | @EventHandler 41 | public void onTestEvent2(ExampleEvent2 e) 42 | { 43 | System.out.println("TestEvent2 "+this); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MVP-Lite 2 | ========= 3 | 4 | MVP-Lite is a Vaadin add on to build Model-View-Presenter based applications. You can find a tutorial in the wiki. 5 | 6 | We built an demo Vaadin-App that simulates an web-email client. Durring this tutorial we try to explain the most components and make examples by refering to this example app. The complete code of the demo app can be found [here](https://github.com/sockeqwe/Vaadin-MVP-Lite-MailExample) on github. At the end of this tutorial you should be able to use our MVP-Lite framework to build well designed (software pattern) Vaadin applications. 7 | 8 | 9 | # Chapters 10 | 11 | 1. [It all starts with the EventBus](https://github.com/sockeqwe/Vaadin-MVP-Lite/wiki/It-all-starts-with-the-EventBus) 12 | 2. [Model-View-Presenter: The Basics](https://github.com/sockeqwe/Vaadin-MVP-Lite/wiki/Model-View-Presenter:-The-Basics) 13 | 3. [Model-View-Presenter: Using MVP-Lite](https://github.com/sockeqwe/Vaadin-MVP-Lite/wiki/Model-View-Presenter:-Using-MVP-Lite) 14 | 4. [View-Hierarchy and ShowViewEvents](https://github.com/sockeqwe/Vaadin-MVP-Lite/wiki/View-Hierarchy-and-ShowViewEvent) 15 | 5. [Navigation](https://github.com/sockeqwe/Vaadin-MVP-Lite/wiki/View-Navigation) 16 | 6. [GlobalEventBus: Real time communication](https://github.com/sockeqwe/Vaadin-MVP-Lite/wiki/GlobalEventBus:-Real-time-communication) 17 | -------------------------------------------------------------------------------- /src/main/java/com/mvplite/event/ShowViewEvent.java: -------------------------------------------------------------------------------- 1 | package com.mvplite.event; 2 | 3 | /** 4 | * This is the base event class for events that should show a View 5 | * @author Hannes Dorfmann 6 | * 7 | */ 8 | public class ShowViewEvent extends Event { 9 | 10 | 11 | private static final long serialVersionUID = 2227091598650502970L; 12 | 13 | 14 | /** 15 | * The dataParameter is used for the upcomming bookmarkable feature. 16 | * Meanwhile for the LiteNavigationController this is not in use so far. 17 | */ 18 | private String dataParameter; 19 | 20 | /** 21 | * Set the dataParameter, that is extraceted by the uri. This 22 | * Note: This method is called by the BookmarkableNavigationController, 23 | * which is currently not implementes. Therefore this method is currently not in use, 24 | * but will be used in 25 | * the future when bookmarkable uris are supported 26 | */ 27 | public void setDataParamerter(String dataParameter){ 28 | this.dataParameter = dataParameter; 29 | } 30 | 31 | 32 | /** 33 | * Get the dataParameter, that is extraceted by the uri. 34 | * Note: This method is currently not used, but will be used in 35 | * the future when bookmarkable uris are supported 36 | */ 37 | public String getDataParameter(){ 38 | return dataParameter; 39 | } 40 | 41 | 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | MVPVaadin 4 | 5 | 6 | Vaadin production mode 7 | productionMode 8 | false 9 | 10 | 11 | Vaadinmvp Application 12 | com.vaadin.terminal.gwt.server.ApplicationServlet 13 | 14 | 15 | Vaadin application class to start 16 | application 17 | com.mvplite.VaadinmvpApplication 18 | 19 | 20 | 21 | Vaadinmvp Application 22 | /* 23 | 24 | 25 | index.html 26 | index.htm 27 | index.jsp 28 | default.html 29 | default.htm 30 | default.jsp 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/main/java/com/mvplite/view/NavigationController.java: -------------------------------------------------------------------------------- 1 | package com.mvplite.view; 2 | 3 | import com.mvplite.event.EventBus; 4 | import com.mvplite.event.EventHandler; 5 | import com.mvplite.event.Show404ViewEvent; 6 | import com.mvplite.view.ui.Breadcrumbs; 7 | 8 | public interface NavigationController { 9 | 10 | /** 11 | * Should a {@link Show404ViewEvent} be fired, 12 | * if the URL is unknown? 13 | * The {@link Show404ViewEvent} can be handled via the normal {@link EventBus} 14 | * and {@link EventHandler} mechanisms 15 | * @param fireShow404ViewEvent true or false 16 | */ 17 | public abstract void setFire404OnUnknownUriFragment( 18 | boolean fireShow404ViewEvent); 19 | 20 | public abstract void addListener(NavigationControllerListener l); 21 | 22 | public abstract void removeListener(NavigationControllerListener l); 23 | 24 | /** 25 | * Set the current View ({@link NavigateableView}). 26 | * This method also invokes the URL generation and 27 | * the corresponding {@link Breadcrumbs} elements. 28 | * @param view 29 | */ 30 | public abstract void setCurrentView(NavigateableView view); 31 | 32 | public abstract EventBus getEventBus(); 33 | 34 | /** 35 | * BETA: 36 | * Sets the view, that should be called, 37 | * if the empty Fragment (url without "#" or with hash but without continued Fragment) 38 | * @param view 39 | */ 40 | public abstract void setStartView(NavigateableView view); 41 | 42 | } -------------------------------------------------------------------------------- /src/main/java/com/mvplite/presenter/Presenter.java: -------------------------------------------------------------------------------- 1 | package com.mvplite.presenter; 2 | 3 | import java.io.Serializable; 4 | 5 | import com.mvplite.event.Event; 6 | import com.mvplite.event.EventBus; 7 | import com.mvplite.view.View; 8 | 9 | /** 10 | * This is the base class for every {@link Presenter} and it provides the basic 11 | * functionallity that is normally used with this model-view-presenter framework. 12 | * 13 | * @see #getView() 14 | * @see #getEventBus() 15 | * @author Hannes Dorfmann 16 | * 17 | * @param 18 | */ 19 | public class Presenter implements Serializable { 20 | 21 | private static final long serialVersionUID = -8062395775037001922L; 22 | 23 | private T view; 24 | private EventBus eventBus; 25 | 26 | public Presenter(){ 27 | 28 | } 29 | 30 | public Presenter (T view) 31 | { 32 | setView(view); 33 | } 34 | 35 | public Presenter(T view, EventBus eventBus) 36 | { 37 | setView(view); 38 | setEventBus(eventBus); 39 | } 40 | 41 | /** 42 | * Get the {@link View} that is associated to this presenter 43 | */ 44 | public T getView(){ 45 | return view; 46 | } 47 | 48 | /** 49 | * Set the view 50 | */ 51 | public void setView(T view){ 52 | this.view = view; 53 | } 54 | 55 | /** 56 | * Get the {@link EventBus} to fire any {@link Event}s you want to 57 | */ 58 | public EventBus getEventBus() { 59 | return eventBus; 60 | } 61 | 62 | /** 63 | * Set the {@link EventBus} 64 | */ 65 | public void setEventBus(EventBus eventBus) { 66 | this.eventBus = eventBus; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/mvplite/view/ui/ArrowBreadcrumbElementFactory.java: -------------------------------------------------------------------------------- 1 | package com.mvplite.view.ui; 2 | 3 | import com.mvplite.view.NavigateableView; 4 | import com.mvplite.view.NavigationController; 5 | import com.mvplite.view.ui.Breadcrumbs.BreadcrumbElementFactory; 6 | import com.vaadin.ui.Button; 7 | import com.vaadin.ui.Button.ClickListener; 8 | import com.vaadin.ui.Component; 9 | import com.vaadin.ui.Button.ClickEvent; 10 | import com.vaadin.ui.themes.BaseTheme; 11 | 12 | public class ArrowBreadcrumbElementFactory implements BreadcrumbElementFactory{ 13 | 14 | private static final long serialVersionUID = -2513158352408693591L; 15 | 16 | private static String elementStyleName = "niceArrowElement"; 17 | private static String firstElementStyleName = "niceArrowElement-first"; 18 | private static String elementZIndexHelper = "niceArrowElementZIndex"; 19 | 20 | public Component createElement(final NavigationController controller, 21 | final NavigateableView view, int index, int count) { 22 | 23 | Button b = new Button(view.getBreadcrumbTitle()); 24 | b.setStyleName(BaseTheme.BUTTON_LINK); 25 | b.addStyleName(elementStyleName); 26 | b.addStyleName(elementZIndexHelper+(20-index)); 27 | b.setSizeUndefined(); 28 | 29 | b.addListener(new ClickListener() { 30 | private static final long serialVersionUID = 5571355154543327126L; 31 | public void buttonClick(ClickEvent event) { 32 | controller.getEventBus().fireEvent(view.getEventToShowThisView()); 33 | } 34 | }); 35 | 36 | if (index == 0) 37 | b.addStyleName(firstElementStyleName); 38 | 39 | return b; 40 | } 41 | 42 | public void updateButtonTexts(Button button, NavigateableView view) { 43 | // TODO Auto-generated method stub 44 | 45 | } 46 | 47 | public void setBreadcrumbElementStyleName(String styleName) { 48 | // TODO Auto-generated method stub 49 | 50 | } 51 | 52 | 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/mvplite/event/RefresherGlobalEventBusDispatcher.java: -------------------------------------------------------------------------------- 1 | package com.mvplite.event; 2 | 3 | import java.util.List; 4 | import com.github.wolfie.refresher.Refresher; 5 | import com.github.wolfie.refresher.Refresher.RefreshListener; 6 | 7 | 8 | public class RefresherGlobalEventBusDispatcher extends Refresher 9 | implements GlobalEventBusDispatcher, 10 | RefreshListener{ 11 | 12 | private static final long serialVersionUID = -3752482867332351643L; 13 | 14 | 15 | private class RefreshThread extends Thread{ 16 | 17 | public void run(){ 18 | 19 | isRunning = true; 20 | 21 | while(isRunning) 22 | { 23 | receivedEvents = GlobalEventBus.getEventsFor(username, sessionId); 24 | try { 25 | sleep(SLEEP_TIME); 26 | } catch (InterruptedException e) { 27 | isRunning = false; 28 | } 29 | } 30 | } 31 | } 32 | 33 | 34 | private Thread refreshThread; 35 | private EventBus localEventBus; 36 | private boolean isRunning; 37 | private List groupNames; 38 | private String username, sessionId; 39 | 40 | private List receivedEvents; 41 | 42 | public static final int REFRESH_INTERVAL = 1500; 43 | private static final long SLEEP_TIME = REFRESH_INTERVAL; 44 | 45 | public RefresherGlobalEventBusDispatcher(String username, String sessionId, 46 | List groupNames, EventBus localEventBus){ 47 | this.localEventBus = localEventBus; 48 | this.sessionId = sessionId; 49 | this.username = username; 50 | this.groupNames = groupNames; 51 | this.addListener(this); 52 | this.setRefreshInterval(REFRESH_INTERVAL); 53 | } 54 | 55 | 56 | 57 | public void start(){ 58 | GlobalEventBus.addClient(username, sessionId, groupNames); 59 | refreshThread = new RefreshThread(); 60 | refreshThread.start(); 61 | } 62 | 63 | 64 | public void stop(){ 65 | GlobalEventBus.removeClient(username, sessionId); 66 | refreshThread.interrupt(); 67 | 68 | } 69 | 70 | 71 | 72 | @Override 73 | public void refresh(Refresher source) { 74 | if (receivedEvents!=null){ 75 | for (com.mvplite.event.Event e : receivedEvents) 76 | localEventBus.fireEvent(e); 77 | } 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | mvp-lite 6 | com.mvplite 7 | 2.0 8 | jar 9 | Module for work with media 10 | 11 | 12 | UTF-8 13 | 7.0.4 14 | 15 | 16 | 17 | org.vaadin.addons 18 | refresher 19 | 1.2.1.7 20 | 21 | 22 | 23 | com.vaadin 24 | vaadin-server 25 | ${vaadin.version} 26 | 27 | 28 | com.vaadin 29 | vaadin-client-compiled 30 | ${vaadin.version} 31 | 32 | 33 | com.vaadin 34 | vaadin-client 35 | ${vaadin.version} 36 | provided 37 | 38 | 39 | com.vaadin 40 | vaadin-themes 41 | ${vaadin.version} 42 | 43 | 44 | 45 | junit 46 | junit 47 | 4.10 48 | test 49 | 50 | 51 | 52 | 53 | 54 | vaadin-addons 55 | http://maven.vaadin.com/vaadin-addons 56 | 57 | 58 | 59 | src/main/test 60 | 61 | 62 | 63 | org.apache.maven.plugins 64 | maven-compiler-plugin 65 | 2.3.2 66 | 67 | 1.7 68 | 1.7 69 | UTF-8 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /src/main/webapp/VAADIN/themes/img/testDiv.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Google Navigator 7 | 108 | 109 | 110 | 111 |

Google example: 112 |

113 |
114 | Help home 115 | Upload or download files 116 | Number 3 117 | 118 |
119 | 120 | -------------------------------------------------------------------------------- /target/surefire-reports/TEST-test.EventBusTest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/main/webapp/VAADIN/themes/img/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Google Navigator 7 | 119 | 120 | 121 | 122 |

Google example: 123 | http://support.google.com/docs/bin/answer.py?hl=en&answer=176692 124 |

125 |
126 | 132 |
133 | 134 | -------------------------------------------------------------------------------- /src/main/java/com/mvplite/view/LiteNavigationController.java: -------------------------------------------------------------------------------- 1 | package com.mvplite.view; 2 | 3 | import java.io.Serializable; 4 | import java.util.LinkedHashMap; 5 | import java.util.LinkedHashSet; 6 | import java.util.LinkedList; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.Set; 10 | 11 | import com.mvplite.event.EventBus; 12 | import com.mvplite.event.Show404ViewEvent; 13 | import com.vaadin.server.Page; 14 | 15 | /** 16 | * The {@link LiteNavigationController} is the simplest {@link NavigationController}. 17 | * The {@link LiteNavigationController} supports Browser history (back and forward) by generating 18 | * uri fragments.
19 | * Note: the uri fragments does not contain any state and therefore bookmarks are 20 | * not supported by the {@link LiteNavigationController}. 21 | * In the future a BookmarkableNavigationController will be implemented to support bookmarks. 22 | * @author Hannes Dorfmann 23 | * 24 | */ 25 | public class LiteNavigationController implements NavigationController, Page.UriFragmentChangedListener { 26 | 27 | 28 | private static final long serialVersionUID = 2585755661712329836L; 29 | 30 | private class HistoryEntry implements Serializable{ 31 | 32 | private static final long serialVersionUID = 6852274305903891448L; 33 | 34 | public List eventsToFire; 35 | 36 | public HistoryEntry(){ 37 | eventsToFire = new LinkedList<>(); 38 | } 39 | } 40 | 41 | private EventBus eventBus; 42 | private final Map historyStack; 43 | private final Set listeners; 44 | private boolean fire404OnUnknownURI; 45 | private boolean setCurrentViewCausedByHistoryChange; 46 | private final Page page; 47 | 48 | 49 | public LiteNavigationController(){ 50 | super(); 51 | historyStack = new LinkedHashMap<>(); 52 | listeners = new LinkedHashSet<>(); 53 | setCurrentViewCausedByHistoryChange = false; 54 | this.page = Page.getCurrent(); 55 | page.addUriFragmentChangedListener(this); 56 | // page.getUriFragment() 57 | setFire404OnUnknownUriFragment(false); 58 | } 59 | 60 | public LiteNavigationController(EventBus eventBus){ 61 | this(); 62 | this.eventBus = eventBus; 63 | } 64 | 65 | 66 | 67 | @Override 68 | public EventBus getEventBus(){ 69 | return eventBus; 70 | } 71 | 72 | public void setEventBus(EventBus eb){ 73 | this.eventBus = eb; 74 | } 75 | 76 | /* (non-Javadoc) 77 | * @see com.mvplite.view.NavigationController#setShowErrorMessageOnUnknownUriFragment(boolean) 78 | */ 79 | @Override 80 | public void setFire404OnUnknownUriFragment(boolean showErrorMessage){ 81 | this.fire404OnUnknownURI = showErrorMessage; 82 | } 83 | 84 | 85 | 86 | /* (non-Javadoc) 87 | * @see com.mvplite.view.NavigationController#addListener(com.mvplite.view.LiteNavigationController.NavigationControllerListener) 88 | */ 89 | @Override 90 | public void addListener(NavigationControllerListener l){ 91 | listeners.add(l); 92 | } 93 | 94 | 95 | /* (non-Javadoc) 96 | * @see com.mvplite.view.NavigationController#removeListener(com.mvplite.view.LiteNavigationController.NavigationControllerListener) 97 | */ 98 | @Override 99 | public void removeListener(NavigationControllerListener l){ 100 | listeners.remove(l); 101 | } 102 | 103 | 104 | private List calculateEventsToFireList(NavigateableView view){ 105 | List events = 106 | new LinkedList(); 107 | 108 | while (view != null) 109 | { 110 | events.add(0, view.getEventToShowThisView()); 111 | 112 | if (view instanceof NavigateableSubView) 113 | view = ((NavigateableSubView) view).getParentView(); 114 | else 115 | view = null; 116 | } 117 | 118 | return events; 119 | } 120 | 121 | public void clearUriFragments(){ 122 | page.setUriFragment("", false); 123 | } 124 | 125 | private String calculateUri(NavigateableView view){ 126 | String uri =""; 127 | 128 | while (view != null){ 129 | uri = "/"+view.getUriFragment()+uri; 130 | 131 | if (view instanceof NavigateableSubView) 132 | view = ((NavigateableSubView) view).getParentView(); 133 | else 134 | view = null; 135 | } 136 | 137 | return uri; 138 | } 139 | 140 | /* (non-Javadoc) 141 | * @see com.mvplite.view.NavigationController#setCurrentView(com.mvplite.view.NavigateableView) 142 | */ 143 | @Override 144 | public void setCurrentView(NavigateableView view){ 145 | 146 | if (!setCurrentViewCausedByHistoryChange){ 147 | // Add url fragmentHistory support 148 | String uriFragment = calculateUri(view); 149 | HistoryEntry entry = new HistoryEntry(); 150 | entry.eventsToFire = calculateEventsToFireList(view); 151 | 152 | if (historyStack.isEmpty()) 153 | historyStack.put("", entry); 154 | 155 | historyStack.put(uriFragment, entry); 156 | page.setUriFragment(uriFragment, false); // Seems not to work at first call 157 | } 158 | fireNavigatedTo(view); 159 | } 160 | 161 | private void fireNavigatedTo(NavigateableView view){ 162 | for (NavigationControllerListener l : listeners) 163 | l.onNavigatedTo(view); 164 | } 165 | 166 | 167 | /* (non-Javadoc) 168 | * @see com.mvplite.view.NavigationController#setStartView(com.mvplite.view.NavigateableView) 169 | */ 170 | @Override 171 | public void setStartView(NavigateableView view){ 172 | HistoryEntry entry = new HistoryEntry(); 173 | entry.eventsToFire = calculateEventsToFireList(view); 174 | 175 | historyStack.put("", entry); 176 | } 177 | 178 | 179 | 180 | @Override 181 | public void uriFragmentChanged(Page.UriFragmentChangedEvent source) { 182 | 183 | // used by the back and forward browser button 184 | 185 | if (source == null) 186 | return; 187 | 188 | String uriFragment = source.getUriFragment(); 189 | 190 | HistoryEntry entry = historyStack.get(uriFragment); 191 | 192 | if (entry == null){ 193 | 194 | if (fire404OnUnknownURI){ 195 | eventBus.fireEvent(new Show404ViewEvent()); 196 | } 197 | 198 | } 199 | else 200 | { 201 | setCurrentViewCausedByHistoryChange = true; 202 | 203 | // fire the events that are needed to get to the state of uri fragment 204 | for (com.mvplite.event.Event e : entry.eventsToFire){ 205 | eventBus.fireEvent(e); 206 | } 207 | 208 | setCurrentViewCausedByHistoryChange = false; 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/main/java/com/mvplite/event/GlobalEventBus.java: -------------------------------------------------------------------------------- 1 | package com.mvplite.event; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Date; 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.TreeSet; 8 | 9 | 10 | public final class GlobalEventBus { 11 | 12 | /** 13 | * The Client represents a Client, since a user can have more than one clients (Browser-windows) 14 | * opened at the same time. 15 | * @author Hannes Dorfmann 16 | * 17 | */ 18 | public final static class Client implements Comparable { 19 | public String sessionID; 20 | public long lastAccess; 21 | public List groupMemberships; 22 | public List queuedEvents; 23 | 24 | public Client(String sessionID, List groupMemberships){ 25 | this.sessionID = sessionID; 26 | this.groupMemberships = groupMemberships; 27 | this.lastAccess = new Date().getTime(); 28 | this.queuedEvents = new ArrayList(); 29 | } 30 | 31 | 32 | @Override 33 | public int compareTo(Client o) { 34 | return this.sessionID.compareTo(o.sessionID); 35 | } 36 | 37 | @Override 38 | public String toString(){ 39 | return queuedEvents.size()+ " "+ queuedEvents.toString()+" "+super.toString(); 40 | } 41 | 42 | } 43 | 44 | 45 | private static long MEMORY_CLEAN_TIMEOUT = 5 * 60 * 1000; 46 | private static long CLIENT_TIMEOUT = 1 * 60 * 1000; 47 | private static long lastCleanUp = new Date().getTime(); 48 | 49 | 50 | private static HashMap > userClientMap = 51 | new HashMap>(); 52 | 53 | private static HashMap> groupClientMap = 54 | new HashMap>(); 55 | 56 | public static void addClient(String username, String sessionID, 57 | List groupMemberships){ 58 | 59 | Client c = new Client(sessionID, groupMemberships); 60 | 61 | TreeSet clientsOfUser = userClientMap.get(username); 62 | 63 | if (clientsOfUser == null) // No other Client of the same user is present 64 | { // TODO synchornized Thread Safe 65 | clientsOfUser = (new TreeSet()); 66 | userClientMap.put(username, clientsOfUser); 67 | } 68 | 69 | clientsOfUser.add(c); 70 | 71 | if (groupMemberships!=null) 72 | for (String name : groupMemberships) 73 | { 74 | List group = groupClientMap.get(name); 75 | if (group == null){ 76 | group = new ArrayList(); 77 | groupClientMap.put(name, group); 78 | } 79 | 80 | group.add(c); 81 | } 82 | 83 | } 84 | 85 | 86 | public static void removeClient(String username, String sessionID){ 87 | 88 | TreeSet clientsOfUser = userClientMap.get(username); 89 | Client toRemove = null; 90 | 91 | if (clientsOfUser != null){ 92 | toRemove = null; 93 | for (Client c: clientsOfUser){ 94 | if (c.sessionID.equals(sessionID)) 95 | { 96 | toRemove = c; 97 | break; 98 | } 99 | 100 | } 101 | 102 | if (toRemove != null) 103 | clientsOfUser.remove(toRemove); 104 | } 105 | 106 | 107 | // remove group membershipments 108 | if (toRemove != null) 109 | for (String groupName : toRemove.groupMemberships) 110 | { 111 | List groups = groupClientMap.get(groupName); 112 | 113 | if (groups!=null) 114 | for (Client client : groups) 115 | if (client.sessionID.equals(sessionID)) 116 | { 117 | toRemove = client; 118 | break; 119 | } 120 | 121 | groups.remove(toRemove); 122 | } 123 | } 124 | 125 | 126 | 127 | 128 | /** 129 | * Fire a event which is delivered to all clients of the user (identified by the username) 130 | * @param username 131 | * @param event 132 | */ 133 | public static void fireEventToUser(String username, Event event){ 134 | 135 | memoryCleanUp(); 136 | 137 | TreeSet clients = userClientMap.get(username); 138 | 139 | if (clients != null) // If there is at least one client 140 | { 141 | for (Client c : clients) 142 | c.queuedEvents.add(event); 143 | } 144 | 145 | } 146 | 147 | /** 148 | * Fire a {@link Event} (broadcast) to every client, excepted the sender himself 149 | * (which is identified by the session Id) 150 | * @param event 151 | * @param sessionIdOfSender 152 | */ 153 | public static void fireBroadcastEvent(Event event, 154 | String sessionIdOfSender){ 155 | 156 | memoryCleanUp(); 157 | 158 | for (TreeSet e : userClientMap.values()) 159 | { 160 | for (Client c: e) 161 | if (c.sessionID.equals(sessionIdOfSender)) 162 | continue; 163 | else 164 | c.queuedEvents.add(event); 165 | } 166 | 167 | } 168 | 169 | /** 170 | * Fire a {@link Event} (broadcast) to every registered Client. 171 | * The {@link Event} is also delivered to the sender. 172 | * @param event 173 | */ 174 | public static void fireBroadcastEvent(Event event){ 175 | 176 | memoryCleanUp(); 177 | 178 | for (TreeSet e : userClientMap.values()) 179 | { 180 | for (Client c: e) 181 | c.queuedEvents.add(event); 182 | } 183 | 184 | } 185 | 186 | /** 187 | * Fire the {@link Event} to all group-member-clients excluding the sender itself 188 | * @param e 189 | * @param sessionIdOfSender 190 | */ 191 | public static void fireGroupBroadcastEvent(Event e, 192 | String groupName, String sessionIdOfSender){ 193 | 194 | memoryCleanUp(); 195 | 196 | List clients = groupClientMap.get(groupName); 197 | 198 | if (clients!=null) 199 | for (Client c: clients) 200 | if (sessionIdOfSender.equals(c.sessionID)) 201 | continue; 202 | else 203 | c.queuedEvents.add(e); 204 | } 205 | 206 | 207 | /** 208 | * Fire a {@link Event} to every client, 209 | * that is member of a group (identified by the passed groupName) 210 | * @param e 211 | */ 212 | public static void fireGroupBroadcastEvent(Event e, 213 | String groupName){ 214 | 215 | memoryCleanUp(); 216 | 217 | List clients = groupClientMap.get(groupName); 218 | if (clients!=null) 219 | for (Client c: clients) 220 | c.queuedEvents.add(e); 221 | } 222 | 223 | /** 224 | * Retrieve all queued {@link Event}s for a client. 225 | * This method is used by {@link GlobalEventBusDispatcher}. 226 | * @param username The username 227 | * @param sessionID the session id of the client 228 | * @return {@link List} of {@link Event}s or null 229 | */ 230 | public static List getEventsFor(String username, String sessionID){ 231 | 232 | TreeSet clients = userClientMap.get(username); 233 | 234 | if (clients != null) 235 | { 236 | for (Client c: clients) 237 | if (c.sessionID.equals(sessionID)){ 238 | List ret = 239 | new ArrayList(c.queuedEvents); 240 | 241 | c.queuedEvents.clear(); 242 | c.lastAccess = new Date().getTime(); 243 | return ret; 244 | } 245 | 246 | } 247 | 248 | 249 | return null; 250 | } 251 | 252 | 253 | private static void memoryCleanUp(){ 254 | 255 | long timeStamp = new Date().getTime(); 256 | 257 | if (lastCleanUp + MEMORY_CLEAN_TIMEOUT < timeStamp){ 258 | 259 | for (TreeSet clients : userClientMap.values()){ 260 | 261 | List toRemove = new ArrayList(); 262 | 263 | for (Client c: clients) 264 | if (c.lastAccess + CLIENT_TIMEOUT < timeStamp){ 265 | toRemove.add(c); 266 | c.queuedEvents.clear(); 267 | 268 | // Remove memberships 269 | for (String groupName : c.groupMemberships) 270 | { 271 | List s = groupClientMap.get(groupName); 272 | s.remove(c); 273 | } 274 | } 275 | 276 | if (!toRemove.isEmpty()) 277 | { 278 | clients.removeAll(toRemove); 279 | } 280 | } 281 | 282 | lastCleanUp = new Date().getTime(); 283 | } 284 | 285 | } 286 | 287 | } 288 | -------------------------------------------------------------------------------- /src/main/java/com/mvplite/view/ui/Breadcrumbs.java: -------------------------------------------------------------------------------- 1 | package com.mvplite.view.ui; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | 6 | import com.mvplite.view.NavigateableSubView; 7 | import com.mvplite.view.NavigateableView; 8 | import com.mvplite.view.NavigationController; 9 | import com.mvplite.view.NavigationControllerListener; 10 | import com.vaadin.ui.Button; 11 | import com.vaadin.ui.Button.ClickEvent; 12 | import com.vaadin.ui.Component; 13 | import com.vaadin.ui.CssLayout; 14 | import com.vaadin.ui.Label; 15 | import com.vaadin.ui.themes.BaseTheme; 16 | 17 | /** 18 | * This is a UI Component, that displays breadcrumbs for the 19 | * {@link NavigateableView} / {@link NavigateableSubView}, which is currently 20 | * displayed on screen. 21 | * To customize the look of this component, you should write your own 22 | * {@link SeparatorFactory} and {@link BreadcrumbElementFactory} and set it with 23 | * {@link #setBreadcrumbElementFactory(BreadcrumbElementFactory)} 24 | * {@link #setSeparatorFactory(SeparatorFactory)} 25 | * 26 | * @author Hannes Dorfmann 27 | */ 28 | public class Breadcrumbs extends CssLayout implements NavigationControllerListener{ 29 | 30 | 31 | private static final long serialVersionUID = -7308648462712616003L; 32 | 33 | public static String BREADCRUMB_ELEMENT = "breadcrumb-element"; 34 | 35 | public static String BREADCRUMB_BAR = "breadcrumb-bar"; 36 | 37 | 38 | /** 39 | * Factory interface for creating breadcrumb separators. 40 | * 41 | * @see Breadcrumbs#setSeparatorFactory(SeparatorFactory) 42 | * @author Petter Holmström 43 | * @since 1.0 44 | */ 45 | public interface SeparatorFactory extends java.io.Serializable { 46 | /** 47 | * Creates and returns a component to be used as a separator between 48 | * breadcrumbs. 49 | */ 50 | Component createSeparator(); 51 | } 52 | 53 | /** 54 | * Default implementation of {@link SeparatorFactory}. The separators are 55 | * labels containing the "»" character and having the 56 | * {@link Breadcrumbs#BREADCRUMB_ELEMENT} style. 57 | * 58 | * @author Petter Holmström 59 | * @since 1.0 60 | */ 61 | public class DefaultSeparatorFactory implements SeparatorFactory { 62 | 63 | private static final long serialVersionUID = 7957216244739746986L; 64 | 65 | @Override 66 | public Component createSeparator() { 67 | final Label separator = new Label("»"); 68 | separator.setSizeUndefined(); 69 | separator.addStyleName(BREADCRUMB_ELEMENT); 70 | return separator; 71 | } 72 | } 73 | 74 | /** 75 | * Factory interface for creating breadcrumb buttons. 76 | * 77 | * @see Breadcrumbs#setBreadcrumbElementFactory(BreadcrumbElementFactory) 78 | * @author Petter Holmström 79 | * @since 1.0 80 | */ 81 | public interface BreadcrumbElementFactory extends java.io.Serializable { 82 | 83 | /** 84 | * Creates and returns a gui component for the specified view. 85 | * @param controller 86 | * @param view 87 | * @param currentIndex the index beginning by zero to totalcount-1 88 | * @param totalCount The total count of breadcrumb elements 89 | */ 90 | public Component createElement(NavigationController controller, NavigateableView view, int currentIndex, int totalCount); 91 | 92 | /** 93 | * Updates the button texts. This method is called when the display name 94 | * and/or the description of the specified view are changed. 95 | * 96 | */ 97 | @Deprecated 98 | void updateButtonTexts(Button button, NavigateableView view); 99 | 100 | 101 | public void setBreadcrumbElementStyleName(String styleName); 102 | } 103 | 104 | 105 | 106 | /** 107 | * Default implementation of {@link BreadcrumbElementFactory}. The created buttons have 108 | * the {@link BaseTheme#BUTTON_LINK} and 109 | * {@link Breadcrumbs#BREADCRUMB_ELEMENT} styles. 110 | * 111 | * @author Petter Holmström 112 | * @since 1.0 113 | */ 114 | public class DefaultBreadcrumbElementFactory implements BreadcrumbElementFactory { 115 | 116 | private static final long serialVersionUID = 8031407455065485896L; 117 | 118 | @Override 119 | public Component createElement(final NavigationController controller, final NavigateableView view, int currentIndex, int totalCount) { 120 | final Button btn = new Button(); 121 | btn.setStyleName(BaseTheme.BUTTON_LINK); 122 | btn.setSizeUndefined(); 123 | btn.addStyleName(BREADCRUMB_ELEMENT); 124 | updateButtonTexts(btn, view); 125 | 126 | btn.addListener(new Button.ClickListener() { 127 | 128 | private static final long serialVersionUID = -9116612359809246223L; 129 | 130 | @Override 131 | public void buttonClick(ClickEvent event) { 132 | controller.getEventBus().fireEvent(view.getEventToShowThisView()); 133 | } 134 | }); 135 | 136 | return btn; 137 | } 138 | 139 | @Override 140 | public void updateButtonTexts(Button button, NavigateableView view) { 141 | button.setCaption(view.getBreadcrumbTitle()); 142 | } 143 | 144 | @Override 145 | public void setBreadcrumbElementStyleName(String styleName) { 146 | BREADCRUMB_ELEMENT = styleName; 147 | } 148 | } 149 | 150 | 151 | private SeparatorFactory separatorFactory; 152 | private BreadcrumbElementFactory elementFactory; 153 | private final NavigationController navigationController; 154 | 155 | public Breadcrumbs(NavigationController controller){ 156 | elementFactory = new DefaultBreadcrumbElementFactory(); 157 | separatorFactory = new DefaultSeparatorFactory(); 158 | this.setSizeUndefined(); 159 | controller.addListener(this); 160 | this.navigationController = controller; 161 | this.setStyleName(BREADCRUMB_BAR); 162 | } 163 | 164 | 165 | 166 | /** 167 | * Set the position / alignment of the breadcrumbs-list in the whole {@link Breadcrumbs} 168 | * @param alignment {@link Alignment} 169 | 170 | public void setBreadcrumListAlignment(Alignment alignment){ 171 | this.setComponentAlignment(breadcrumbElementContainer, alignment); 172 | } 173 | */ 174 | 175 | /** 176 | * Returns the separator factory to use for creating separators between 177 | * breadcrumb buttons. 178 | */ 179 | public SeparatorFactory getSeparatorFactory() { 180 | return separatorFactory; 181 | } 182 | 183 | /** 184 | * Sets the separator factory to use for creating separators between 185 | * breadcrumb buttons. Set this value to null to use the 186 | * default separator factory. 187 | */ 188 | public void setSeparatorFactory(SeparatorFactory separatorFactory) { 189 | if (separatorFactory == null) { 190 | separatorFactory = new DefaultSeparatorFactory(); 191 | } 192 | this.separatorFactory = separatorFactory; 193 | } 194 | 195 | /** 196 | * Returns the button factory to use for creating breadcrumb buttons. 197 | */ 198 | public BreadcrumbElementFactory getBreadcrumbElementFactory() { 199 | return elementFactory; 200 | } 201 | 202 | /** 203 | * Sets the button factory to use for creating breadcrumb buttons. Set this 204 | * value to null to use the default button factory. 205 | */ 206 | public void setBreadcrumbElementFactory(BreadcrumbElementFactory buttonFactory) { 207 | if (buttonFactory == null) { 208 | buttonFactory = new DefaultBreadcrumbElementFactory(); 209 | } 210 | this.elementFactory = buttonFactory; 211 | } 212 | 213 | 214 | protected void addBreadcrumbForView(final NavigateableView view, int index, int totalCount) { 215 | 216 | final Component btn = getBreadcrumbElementFactory().createElement(navigationController, view, index, totalCount); 217 | this.addComponent(btn); 218 | // breadcrumbElementContainer.setComponentAlignment(btn, Alignment.MIDDLE_LEFT); 219 | } 220 | 221 | 222 | 223 | 224 | 225 | protected void addSeparatorForView() { 226 | Component separator = getSeparatorFactory().createSeparator(); 227 | if (separator != null){ 228 | this.addComponent(separator); 229 | // breadcrumbElementContainer.setComponentAlignment(separator, Alignment.MIDDLE_LEFT); 230 | } 231 | } 232 | 233 | protected void removeBreadcrumbs() { 234 | this.removeAllComponents(); 235 | 236 | } 237 | 238 | @Override 239 | public void onNavigatedTo(NavigateableView view) { 240 | removeBreadcrumbs(); 241 | generateBreadcrumb(view); 242 | 243 | } 244 | 245 | private void generateBreadcrumb(NavigateableView view){ 246 | 247 | NavigateableView v = view; 248 | List viewPath = new LinkedList(); 249 | 250 | // TODO optimization: do everything in one loop 251 | while (true){ 252 | 253 | viewPath.add(v); 254 | 255 | if (v instanceof NavigateableSubView) 256 | { 257 | v = ((NavigateableSubView) v).getParentView(); 258 | } 259 | else 260 | break; 261 | } 262 | 263 | int totalCount = viewPath.size(); 264 | int index = 0; 265 | for (int i = viewPath.size()-1; i>=0; i--) 266 | { 267 | if (i!=viewPath.size()-1) 268 | addSeparatorForView(); 269 | 270 | addBreadcrumbForView(viewPath.get(i), index, totalCount); 271 | index++; 272 | } 273 | 274 | 275 | 276 | } 277 | 278 | } 279 | -------------------------------------------------------------------------------- /src/main/java/com/mvplite/event/EventBus.java: -------------------------------------------------------------------------------- 1 | package com.mvplite.event; 2 | 3 | import java.io.Serializable; 4 | import java.lang.reflect.InvocationTargetException; 5 | import java.lang.reflect.Method; 6 | import java.util.LinkedHashSet; 7 | import java.util.Map; 8 | import java.util.Set; 9 | import java.util.concurrent.ConcurrentHashMap; 10 | 11 | /** 12 | * @author Hannes Dorfmann 13 | */ 14 | class EventMethodCache implements Serializable { 15 | 16 | 17 | private static final long serialVersionUID = -3835595439788993624L; 18 | 19 | private final Map, Set> methodMap; 20 | 21 | public EventMethodCache() { 22 | methodMap = new ConcurrentHashMap<>(); 23 | } 24 | 25 | 26 | /** 27 | * Adds a Method to the {@link #methodMap} 28 | */ 29 | public void addMethodToCache(Class c, Method m) { 30 | Set methods = methodMap.get(c); 31 | 32 | if (methods == null) { 33 | methods = new LinkedHashSet<>(); 34 | methodMap.put(c, methods); 35 | } 36 | 37 | methods.add(m); 38 | } 39 | 40 | 41 | public void clear() { 42 | methodMap.clear(); 43 | } 44 | 45 | 46 | public void removeCachedOf(Class c) { 47 | methodMap.remove(c); 48 | } 49 | 50 | 51 | public Set getMethods(Class c) { 52 | return methodMap.get(c); 53 | } 54 | 55 | 56 | public boolean isClassCached(Class c) { 57 | return methodMap.containsKey(c); 58 | } 59 | 60 | } 61 | 62 | /** 63 | * The {@link EventDispatcher} is responsible for dispatching / delivering 64 | * a Event to the corresponding {@link EventHandler} - Method. 65 | * This is realized by using reflections, especially {@link Method#invoke(Object, Object...)} 66 | * 67 | * @author Hannes Dorfmann 68 | */ 69 | class EventDispatcher implements Serializable { 70 | 71 | private static final long serialVersionUID = -7359501691640084178L; 72 | 73 | private final Object target; 74 | private final Method method; 75 | 76 | 77 | public EventDispatcher(Object target, Method method) { 78 | this.target = target; 79 | this.method = method; 80 | this.method.setAccessible(true); 81 | } 82 | 83 | public Object getTarget() { 84 | return target; 85 | } 86 | 87 | public void dispatchEvent(Object event) { 88 | 89 | try { 90 | method.invoke(target, event); 91 | } catch (IllegalArgumentException e) { 92 | throw new Error("Method rejected target/argument: " + event, e); 93 | } catch (IllegalAccessException e) { 94 | throw new Error("Method became inaccessible: " + event, e); 95 | } catch (InvocationTargetException e) { 96 | if (e.getCause() instanceof Error) { 97 | throw (Error) e.getCause(); 98 | } else 99 | throw new Error(e); 100 | } 101 | } 102 | 103 | @Override 104 | public int hashCode() { 105 | final int PRIME = 31; 106 | return (PRIME + method.hashCode()) * PRIME 107 | + System.identityHashCode(target); 108 | } 109 | 110 | @Override 111 | public boolean equals(Object o) { 112 | if (o instanceof EventDispatcher) { 113 | EventDispatcher other = (EventDispatcher) o; 114 | return target == other.target && method.equals(other.method); 115 | } 116 | 117 | return false; 118 | } 119 | } 120 | 121 | 122 | /** 123 | * {@link Event}s can be fired to the {@link EventBus} and the {@link EventBus} 124 | * is the component, that deliver / dispatch the {@link Event}s to the registered {@link EventHandler}. 125 | * 126 | * @author Hannes Dorfmann 127 | * @see #fireEvent(Event) 128 | * @see #addHandler(Object) 129 | * @see #removeHandler(Object) 130 | */ 131 | public class EventBus implements Serializable { 132 | 133 | private static final long serialVersionUID = 5500479291713928578L; 134 | 135 | private static final EventMethodCache eventMethodChache = new EventMethodCache(); 136 | private final Map, Set> handlerMap; 137 | 138 | private static boolean caching = true; 139 | 140 | 141 | public EventBus() { 142 | handlerMap = new ConcurrentHashMap<>(); 143 | } 144 | 145 | /** 146 | * Enable or disable caching 147 | */ 148 | public void setUseCache(boolean caching) { 149 | EventBus.caching = caching; 150 | } 151 | 152 | /** 153 | * Get the Class of the {@link Event}. 154 | * The passed {@link Method} must be a valid {@link EventHandler}-annotated 155 | * method with exactly one parameter (the {@link Event}). 156 | * This method is a little helper method and is only used by the {@link EventBus} internally. 157 | */ 158 | @SuppressWarnings("unchecked") 159 | private Class getEventClass(Method m) { 160 | return (Class) m.getParameterTypes()[0]; 161 | } 162 | 163 | /** 164 | * Registers an {@link EventHandler} 165 | */ 166 | public void addHandler(Object handler) { 167 | 168 | boolean added; 169 | 170 | if (caching) { 171 | if (!eventMethodChache.isClassCached(handler.getClass())) 172 | added = scanHandlerAndCreateEventDispatcher(handler); // This class is not cached, so scan the class 173 | else 174 | // This class has been cached (has been already scanned), so build the EventDispatcher from Cache 175 | added = createEventDispatchersFromCache(handler); 176 | } else 177 | added = scanHandlerAndCreateEventDispatcher(handler); 178 | 179 | if (!added) 180 | throw new Error("No @EventHandler annotated Method found in " + handler.getClass().getName()); 181 | 182 | } 183 | 184 | 185 | private boolean scanHandlerAndCreateEventDispatcher(Object handler) { 186 | boolean added = false; 187 | for (Method m : handler.getClass().getMethods()) { 188 | if (!m.isAnnotationPresent(EventHandler.class)) 189 | continue; 190 | 191 | Class params[] = m.getParameterTypes(); 192 | if (params.length == 1 && isEventClass(params[0])) { 193 | // This Method is a Valid EventHandler 194 | EventDispatcher disp = new EventDispatcher(handler, m); 195 | addEventDispatcher(getEventClass(m), disp); 196 | added = true; 197 | 198 | if (caching) 199 | eventMethodChache.addMethodToCache(handler.getClass(), m); 200 | } else 201 | throw new Error("You have annotated the Method " + m.getName() + " with @EventHandler, " + 202 | "but this method did not match the required one Parameter (exactly one) of the type Event"); 203 | } 204 | 205 | return added; 206 | } 207 | 208 | 209 | private boolean isEventClass(Class clazz) { 210 | 211 | Class c = clazz; 212 | while (c != null) { 213 | if (c.equals(Event.class)) 214 | return true; 215 | 216 | c = c.getSuperclass(); 217 | } 218 | 219 | return false; 220 | 221 | } 222 | 223 | /** 224 | * Creates {@link EventDispatcher}s by unsing the {@link EventMethodCache}. 225 | * That means, that the class of the passed handler has already be scanned for 226 | * {@link EventHandler} annotations and all information about building the 227 | * {@link EventDispatcher}s are present in the {@link EventMethodCache}. 228 | */ 229 | private boolean createEventDispatchersFromCache(Object handler) { 230 | 231 | Set methods = eventMethodChache.getMethods(handler.getClass()); 232 | 233 | if (methods == null) 234 | throw new Error("The class " + handler.getClass().getName() + " has not been cached until now. However the EventBus tries to create a EventDispatcher from the cache."); 235 | 236 | for (Method m : methods) { 237 | EventDispatcher disp = new EventDispatcher(handler, m); 238 | addEventDispatcher(getEventClass(m), disp); 239 | } 240 | 241 | return !methods.isEmpty(); 242 | } 243 | 244 | /** 245 | * Add a {@link EventDispatcher} for the passed {@link Event}-Class 246 | */ 247 | private void addEventDispatcher(Class eventClass, 248 | EventDispatcher disp) { 249 | 250 | Set dispatchers = handlerMap.get(eventClass); 251 | 252 | if (dispatchers == null) { 253 | dispatchers = new LinkedHashSet<>(); 254 | handlerMap.put(eventClass, dispatchers); 255 | } 256 | 257 | dispatchers.add(disp); 258 | } 259 | 260 | 261 | /** 262 | * Removes an Handler (a Object with {@link EventHandler} annotated methods) from 263 | * the {@link EventBus}. That means, that future fired {@link Event}s are no longer 264 | * dispatched / delivered to the passed handler 265 | */ 266 | public void removeHandler(Object handler) { 267 | 268 | Set toRemove = new LinkedHashSet<>(); 269 | for (Set dispatchers : handlerMap.values()) { 270 | for (EventDispatcher d : dispatchers) 271 | if (d.getTarget() == handler) 272 | toRemove.add(d); 273 | 274 | dispatchers.removeAll(toRemove); 275 | toRemove.clear(); 276 | } 277 | } 278 | 279 | 280 | /** 281 | * Fires a Event to the EventBus to inform all registered EventHandlers about this Event 282 | * 283 | * @return true if at least one {@link EventHandler} is registered and has received the passed event, 284 | * otherwise false 285 | */ 286 | public boolean fireEvent(Event event) { 287 | 288 | Set dispatchers = handlerMap.get(event.getClass()); 289 | if (dispatchers == null || dispatchers.isEmpty()) 290 | return false; 291 | 292 | for (EventDispatcher disp : dispatchers) 293 | disp.dispatchEvent(event); 294 | 295 | return true; 296 | } 297 | 298 | 299 | } 300 | --------------------------------------------------------------------------------