Monday, February 6, 2012

Using Listener instead of GWT Event Bus

When developers design communication between controllers in GWT applications, they often use Event Bus. It is a good decision if you have at least several event handlers for every event. But what if there are only two controllers? If we prefer Event Bus, then every event will have only one event handler. It is too costly. In this situation it is better to use Listener design pattern (it is also known as Observer).

This post has a Russian version.

Earlier we have created an application with Event Bus. Now let's create a similar project, but using Listener pattern.

File pom.xml is practically the same - application name is the only thing that differs:
<?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>listenerapp</groupId>
    <artifactId>listenerapp</artifactId>
    <version>1.0</version>
    <packaging>war</packaging>
    <name>listenerapp</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>/listenerapp</path>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Create interface ClearListener in com.listenerapp.client.view package. It should contain one method that will be invoked when user clicks "Clear" button:
package com.listenerapp.client.view;

public interface ClearListener {

    void clear();
}

"Clear" button is still implemented as class ButtonView in com.listenerapp.client.view package. But now its constructor takes instance of ClearListener instead of SimpleEventBus as a parameter. When user clicks the button that instance invokes its method clear():
package com.listenerapp.client.view;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.ui.Button;

public class ButtonView extends Button {

    public ButtonView(final ClearListener clearListener) {
        setText("Clear");

        addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                clearListener.clear();
            }
        });
    }
}

Textbox is implemented as class TextBoxView in the same package. That class contains only method clear() that clears textbox contents:
package com.listenerapp.client.view;

import com.google.gwt.user.client.ui.TextBox;

public class TextBoxView extends TextBox {

    public void clear() {
        setText("");
    }
}

Create application main class - ListenerApp in com.listenerapp.client package. ButtonView constructor takes anonymous class object as a parameter. That anonymous class implements ClearListener interface - its method clear() invokes the same named method of TextBoxView instance. So, when "Clear" button is clicked ButtonView instance invokes method clear() of TextBoxView instance, despite the fact that both classes don't know about each other's existence:
package com.listenerapp.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.RootPanel;
import com.listenerapp.client.view.ButtonView;
import com.listenerapp.client.view.ClearListener;
import com.listenerapp.client.view.TextBoxView;

public class ListenerApp implements EntryPoint {
    private ButtonView buttonView;
    private TextBoxView textBoxView;

    @Override
    public void onModuleLoad() {
        textBoxView = new TextBoxView();

        buttonView = new ButtonView(new ClearListener() {
            @Override
            public void clear() {
                textBoxView.clear();
            }
        });

        RootPanel.get().add(buttonView);
        RootPanel.get().add(textBoxView);
    }
}

ListenerApp.gwt.xml module settings file is similar to EventBusApp.gwt.xml:
<?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='listenerapp'>
    <inherits name='com.google.gwt.user.User'/>
    <inherits name='com.google.gwt.user.theme.standard.Standard'/>

    <entry-point class='com.listenerapp.client.ListenerApp'/>

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

Change application start page in web.xml file:
<?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>ListenerApp.html</welcome-file>
    </welcome-file-list>

</web-app>

ListenerApp.html page is similar to EventBusApp.html:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>
<head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <title>ListenerApp</title>
    <script type="text/javascript" language="javascript"
            src="listenerapp/listenerapp.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 listenerapp:listenerapp:war:1.0
[WARNING] 'build.plugins.plugin.version' for org.codehaus.mojo:tomcat-maven-plugin is missing. @ line 56, 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 listenerapp 1.0
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:2.4.1:clean (default-clean) @ listenerapp ---
[INFO] Deleting D:\Work\listenerapp\target
[INFO] 
[INFO] >>> tomcat-maven-plugin:1.1:deploy (default-cli) @ listenerapp >>>
[INFO] 
[INFO] --- maven-resources-plugin:2.4.3:resources (default-resources) @ listenerapp ---
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory D:\Work\listenerapp\src\main\resources
[INFO] 
[INFO] --- maven-compiler-plugin:2.3.2:compile (default-compile) @ listenerapp ---
[WARNING] File encoding has not been set, using platform encoding Cp1252, i.e. build is platform dependent!
[INFO] Compiling 4 source files to D:\Work\listenerapp\target\classes
[INFO] 
[INFO] --- maven-resources-plugin:2.4.3:testResources (default-testResources) @ listenerapp ---
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory D:\Work\listenerapp\src\test\resources
[INFO] 
[INFO] --- maven-compiler-plugin:2.3.2:testCompile (default-testCompile) @ listenerapp ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] --- maven-surefire-plugin:2.10:test (default-test) @ listenerapp ---
[INFO] Tests are skipped.
[INFO] 
[INFO] --- gwt-maven-plugin:2.4.0:compile (default) @ listenerapp ---
[INFO] auto discovered modules [com.listenerapp.ListenerApp]
[INFO] Compiling module com.listenerapp.ListenerApp
[INFO]    Compiling 1 permutation
[INFO]       Compiling permutation 0...
[INFO]    Compile of permutations succeeded
[INFO] Linking into D:\Work\listenerapp\target\listenerapp-1.0\listenerapp
[INFO]    Link succeeded
[INFO]    Compilation succeeded -- 20.720s
[INFO] 
[INFO] --- maven-war-plugin:2.1.1:war (default-war) @ listenerapp ---
[INFO] Packaging webapp
[INFO] Assembling webapp [listenerapp] in [D:\Work\listenerapp\target\listenerapp-1.0]
[INFO] Processing war project
[INFO] Copying webapp resources [D:\Work\listenerapp\src\main\webapp]
[INFO] Webapp assembled in [141 msecs]
[INFO] Building war: D:\Work\listenerapp\target\listenerapp-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) @ listenerapp <<<
[INFO] 
[INFO] --- tomcat-maven-plugin:1.1:deploy (default-cli) @ listenerapp ---
[INFO] Deploying war to http://localhost:8080/listenerapp  
[INFO] OK - Deployed application at context path /listenerapp
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 27.346s
[INFO] Finished at: Sun Jan 29 23:01:31 EET 2012
[INFO] Final Memory: 15M/309M
[INFO] ------------------------------------------------------------------------

Go to http://localhost:8080/listenerapp/. 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/listenerapp/.

No comments:

Post a Comment