Sunday, November 27, 2011

GWT Event Bus basics

Developers ofter create web applications using Model-View-Controller pattern.

This post has a Russian version.

Model is data and methods to work with it. It responds to requests and changes its state accordingly. Model doesn't know how data is rendered.

View renders the Model.

Controller provides a connection between Model and View.

Interaction of these components can be presented like this:


Often it is needed to make several Views cooperate with each other. Imagine that we have TableView with some data table and PrintView with button PrintButton to print reports. When user clicks PrintButton it is needed to collect the data from TableView table, generate a report and provide it to user. Views interact through their Controllers:


Controllers can have references to each other. But what it would be if we had 20 controllers instead of 2? It would lead to high class coupling. It is much better if controllers interact using Event Bus. In this case every controller can create Events and handle them using Event Handlers. Also, controllers do not depend on each other:


Let's create a simple GWT application that uses Event Bus.

Create pom.xml file:

After all that manipulations pom.xml should look like this:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>eventbusapp</groupId>
    <artifactId>eventbusapp</artifactId>
    <version>1.0</version>
    <packaging>war</packaging>
    <name>eventbusapp</name>

    <properties>
        <gwt.version>2.4.0</gwt.version>
        <maven-surefire-plugin.version>2.10</maven-surefire-plugin.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.google.gwt</groupId>
            <artifactId>gwt-servlet</artifactId>
            <version>${gwt.version}</version>
        </dependency>
        <dependency>
            <groupId>com.google.gwt</groupId>
            <artifactId>gwt-user</artifactId>
            <version>${gwt.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>gwt-maven-plugin</artifactId>
                <version>${gwt.version}</version>
                <configuration>
                    <draftCompile>true</draftCompile>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>${maven-surefire-plugin.version}</version>
                <configuration>
                    <skipTests>true</skipTests>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>tomcat-maven-plugin</artifactId>
                <configuration>
                    <url>http://localhost:8080/manager</url>
                    <server>tomcat</server>
                    <path>/eventbusapp</path>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Our application will contain two widgets - "Clear" button and a textbox. Textbox should clear its contents if user clicks the button.

Every time such click is made application should fire new event - ClearEvent. Textbox should handle that event and clear its contents.

Create interface for event handler ClearEventHandler in com.eventbusapp.client.event package. It should contain method clear() that clears textbox contents:
package com.eventbusapp.client.event;

import com.google.gwt.event.shared.EventHandler;

public interface ClearEventHandler extends EventHandler {

    void clear(ClearEvent event);
}

Create event class ClearEvent in the same package. It should contain:
  • variable TYPE that defines event type
  • method getAssociatedType() that returns event type
  • method dispatch() that invokes event handler's method clear()
package com.eventbusapp.client.event;

import com.google.gwt.event.shared.GwtEvent;

public class ClearEvent extends GwtEvent<ClearEventHandler> {

    public static Type<ClearEventHandler> TYPE = new Type<ClearEventHandler>();

    @Override
    public Type<ClearEventHandler> getAssociatedType() {
        return TYPE;
    }

    @Override
    protected void dispatch(ClearEventHandler handler) {
        handler.clear(this);
    }
}

"Clear" button is implemented as class ButtonView in com.eventbusapp.client.view package. Class constructor takes instance of SimpleEventBus as a parameter. When user clicks the button that instance fires new ClearEvent:
package com.eventbusapp.client.view;

import com.eventbusapp.client.event.ClearEvent;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.shared.SimpleEventBus;
import com.google.gwt.user.client.ui.Button;

public class ButtonView extends Button {

    public ButtonView(final SimpleEventBus eventBus) {
        setText("Clear");

        addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                eventBus.fireEvent(new ClearEvent());
            }
        });
    }
}

Textbox is implemented as class TextBoxView in the same package. Class constructor also takes instance of SimpleEventBus as a parameter, but in that case that instance handles ClearEvent by implementing method clear() and clears textbox contents:
package com.eventbusapp.client.view;

import com.eventbusapp.client.event.ClearEvent;
import com.eventbusapp.client.event.ClearEventHandler;
import com.google.gwt.event.shared.SimpleEventBus;
import com.google.gwt.user.client.ui.TextBox;

public class TextBoxView extends TextBox {

    public TextBoxView(SimpleEventBus eventBus) {
        eventBus.addHandler(ClearEvent.TYPE, new ClearEventHandler() {
            @Override
            public void clear(ClearEvent event) {
                setText("");
            }
        });
    }
}

Create application main class - EventBusApp in com.eventbusapp.client package. Create event bus object by creating instance of SimpleEventBus and add our button and textbox to application page:
package com.eventbusapp.client;

import com.eventbusapp.client.view.ButtonView;
import com.eventbusapp.client.view.TextBoxView;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.event.shared.SimpleEventBus;
import com.google.gwt.user.client.ui.RootPanel;

public class EventBusApp implements EntryPoint {

    @Override
    public void onModuleLoad() {
        SimpleEventBus eventBus = new SimpleEventBus();

        RootPanel.get().add(new ButtonView(eventBus));
        RootPanel.get().add(new TextBoxView(eventBus));
    }
}

Create module settings file EventBusApp.gwt.xml in com.eventbusapp package. Set "user.agent" parameter to reduce compile time:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 1.6.2//EN"
        "http://google-web-toolkit.googlecode.com/svn/tags/1.6.2/distro-source/core/src/gwt-module.dtd">

<module rename-to='eventbusapp'>
    <inherits name='com.google.gwt.user.User'/>
    <inherits name='com.google.gwt.user.theme.standard.Standard'/>

    <entry-point class='com.eventbusapp.client.EventBusApp'/>

    <set-property name="user.agent" value="safari"/>
</module>

Create deployment descriptor web.xml in webapp/WEB-INF folder:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
        PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

    <welcome-file-list>
        <welcome-file>EventBusApp.html</welcome-file>
    </welcome-file-list>

</web-app>

Create application page EventBusApp.html in webapp folder:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>
<head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <title>EventBusApp</title>
    <script type="text/javascript" language="javascript" 
            src="eventbusapp/eventbusapp.nocache.js"></script>
</head>
<body>

</body>
</html>

Start Tomcat, build and deploy our application by executing command "mvn clean tomcat:deploy":
[INFO] Scanning for projects...
[WARNING] 
[WARNING] Some problems were encountered while building the effective model for eventbusapp:eventbusapp:war:1.0
[WARNING] 'build.plugins.plugin.version' for org.codehaus.mojo:tomcat-maven-plugin is missing. @ line 55, column 21
[WARNING] 
[WARNING] It is highly recommended to fix these problems because they threaten the stability of your build.
[WARNING] 
[WARNING] For this reason, future Maven versions might no longer support building such malformed projects.
[WARNING] 
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building eventbusapp 1.0
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:2.4.1:clean (default-clean) @ eventbusapp ---
[INFO] Deleting D:\Work\eventbusapp\target
[INFO] 
[INFO] >>> tomcat-maven-plugin:1.1:deploy (default-cli) @ eventbusapp >>>
[INFO] 
[INFO] --- maven-resources-plugin:2.4.3:resources (default-resources) @ eventbusapp ---
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory D:\Work\eventbusapp\src\main\resources
[INFO] 
[INFO] --- maven-compiler-plugin:2.3.2:compile (default-compile) @ eventbusapp ---
[WARNING] File encoding has not been set, using platform encoding Cp1252, i.e. build is platform dependent!
[INFO] Compiling 5 source files to D:\Work\eventbusapp\target\classes
[INFO] 
[INFO] --- maven-resources-plugin:2.4.3:testResources (default-testResources) @ eventbusapp ---
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory D:\Work\eventbusapp\src\test\resources
[INFO] 
[INFO] --- maven-compiler-plugin:2.3.2:testCompile (default-testCompile) @ eventbusapp ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] --- maven-surefire-plugin:2.10:test (default-test) @ eventbusapp ---
[INFO] Tests are skipped.
[INFO] 
[INFO] --- gwt-maven-plugin:2.4.0:compile (default) @ eventbusapp ---
[INFO] auto discovered modules [com.eventbusapp.EventBusApp]
[INFO] Compiling module com.eventbusapp.EventBusApp
[INFO]    Compiling 1 permutation
[INFO]       Compiling permutation 0...
[INFO]    Compile of permutations succeeded
[INFO] Linking into D:\Work\eventbusapp\target\eventbusapp-1.0\eventbusapp
[INFO]    Link succeeded
[INFO]    Compilation succeeded -- 17.117s
[INFO] 
[INFO] --- maven-war-plugin:2.1.1:war (default-war) @ eventbusapp ---
[INFO] Packaging webapp
[INFO] Assembling webapp [eventbusapp] in [D:\Work\eventbusapp\target\eventbusapp-1.0]
[INFO] Processing war project
[INFO] Copying webapp resources [D:\Work\eventbusapp\src\main\webapp]
[INFO] Webapp assembled in [110 msecs]
[INFO] Building war: D:\Work\eventbusapp\target\eventbusapp-1.0.war
[WARNING] Warning: selected war files include a WEB-INF/web.xml which will be ignored 
(webxml attribute is missing from war task, or ignoreWebxml attribute is specified as 'true')
[INFO] 
[INFO] <<< tomcat-maven-plugin:1.1:deploy (default-cli) @ eventbusapp <<<
[INFO] 
[INFO] --- tomcat-maven-plugin:1.1:deploy (default-cli) @ eventbusapp ---
[INFO] Deploying war to http://localhost:8080/eventbusapp  
[INFO] OK - Deployed application at context path /eventbusapp
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 22.834s
[INFO] Finished at: Fri Nov 25 01:11:01 EET 2011
[INFO] Final Memory: 16M/309M
[INFO] ------------------------------------------------------------------------

Go to http://localhost:8080/eventbusapp/. Page with "Clear" button and textbox should be displayed. Enter some text into textbox and click the button. Textbox contents should be cleared:











Folder structure:































Application source code is available at https://code.google.com/p/eventbusapp/.

You can discover more about EventBus by watching that great video and reading that article.

16 comments:

  1. Great Post. Would like to hear more related articles from you...

    ReplyDelete
  2. Great post, excellent for understanding the eventbus concept!

    ReplyDelete
  3. That was just fabulous. The Google MVP example involving EventBus left me completely confused, this one cleared it up just perfect!

    ReplyDelete
  4. Can you explain what is the controller in this context ?

    ReplyDelete
    Replies
    1. In such simple application EventBusApp class performs controller role, e.g. it injects event bus into views

      Delete
  5. Thanks Alex. Great example on the EventBus. Appreciate it lots.

    ReplyDelete