Sample Calculator Application using GWT With RPC Calls

The sample application written adds two numbers or subtracts one number from another. An RPC call
is made from client to server and the actual addition/ subtraction is done on the server side.
The UI of the application looks something like this:

There is a single entry point on the java side and it implements the EntryPoint interface that
must implement the onModuleLoad() method. The war file looks for an xml where this entry point
class is specified usually with the name “appname”.gwt.xml
In the sample application the entry point class is CalculatorServer.java and it is specified as
the entry point class in SampleProject.gwt.xml. It also contains most of the java code that is
rendered into html components, javascript and css. The javascript version of the endpoint class is
generated by gwt and in our case it is called sampleproject.nocache.js. We need not modify this
file.
If we use domain objects that are shared between client and server it is mandatory that they
implement the serializable interface.
However the SampleProject application does not use any such domain objects.
The domain object gets passed between browser and server serialized and deserialized by the framework.
SampleService.java is the interface that extends RemoteService and it is the serverside interface
exposing server side methods. The object is passed in some serialized form, maybe as xml or xml
representation of a java object using xmlhttprequest and xmlhttpresponse. In the sample
application the method exposed on the server side is public String getResult(String num1, String
mathAction, String num2);
It expects as arguments the two numbers on which the mathematical
operation is to be performed upon and the actual mathematical operator.
GWT uses asynchronous communication therefore you also need to create the asynchronous version of
this interface. The name of this interface must be the interface name concatenated with “Async”.
which is what the framework needs. In the sample application the following methods is exposed in
SampleServiceAsync.java:
void getResult(String num1, String mathAction, String num3, AsyncCallback callback);

SampleServiceAsync interface is implemented by framework and our classes don’t implement it.
SampleService.java has this code
@RemoteServiceRelativePath(“userService”)
public interface SampleService extends RemoteService {

“userService” is the alias by which UserService is exposed to the client.
It is specified in web.xml as a servlet as follows:

  <servlet>
     <servlet-name>userServlet</servlet-name>
     <servlet-class>com.mattiz.sample.server.SampleServiceImpl</servlet-class>
  </servlet>  
  <servlet-mapping>
     <servlet-name>userServlet</servlet-name>
     <url-pattern>/sampleproject/userService</url-pattern>
  </servlet-mapping>

Note that userServlet points to the implementation of SampleService which is SampleServiceImpl
which extends RemoteServiceServlet and implements SampleService.
In the SampleServiceImpl the business implementation on the server side is implemented. In the
sample example the addition or subtraction on the parameters passed from the front end is
performed.
The entrypoint class, CalculatorServer.java calls the server side method in the following code:
SampleServiceAsync service = (SampleServiceAsync) GWT
.create(SampleService.class);
ServiceDefTarget serviceDef = (ServiceDefTarget) service;
serviceDef.setServiceEntryPoint(GWT.getModuleBaseURL() + “userService”);
SampleCallback sampleCallback = new SampleCallback(tb);
service.getResult(num1, mathAction, num2, sampleCallback);

Note that the alias of SampleServiceImpl which is sampleproject/userService, is invoked within
CalculatorServer class. The servlet is mapped to a path ending with /userService and the client
side code(javascript) may invoke it. The client code calls this service hidden to us which is how
ajax basically works.
SampleCallBack is the class which sends/ recieves data from the server. SampleCallBack is called
when the service URL is executed by AJAX. The Async service returns this type of callback object
populated with the result of the server side application.
Each service will have its own callback class. The callback class for our server side method is
SampleCallBack implements AsyncCallback
In the sample application it implements two methods:
onFailure(Throwable caught) {
onSuccess(String result) {

onFailure is called on an unsuccessful call to the server. On a successful call to the server side
method, onSuccess() is called. Inside this method the value recieved from the server side call is
populated in the textbox from the parameter of the onSuccess() method.
private TextBox tb.setValue(result);
SampleCallBack is executed when response comes back from server. On failure it pops a window alert
and on success it populates the textfield with data.
SampleCallback sampleCallback = new SampleCallback(textbox);
in the entrypoint class calls the constructor of the SampleCalback class and passes on the texbox
variable to the Callback class.
CalculatorServer entry point class has several buttons, for numbers 0-9 and “+”, “-“.. On clicking
any of these buttons an anonymous inner class is called with the following code
button.addClickHandler(new ClickHandler() {
It is within this class that the data from the UI is transferred to server side through an RPC
call and data is received in a similar way from the callback class and is displayed on the UI.
The response from URL is got in SampleCallback class service.getUserList(myUserCallback);
Note this line in the entry point class:
serviceDef.setServiceEntryPoint(GWT.getModuleBaseURL()
+ “userService”);

This is where the AJAX call is made to the URL on button click and SampleServiceAsync is invoked.
The response from URL is got in SampleCallback class and the textfied contained in SampleCallback
is populated with data.
The response from URL is got in SampleCallback class:
service.getResult(num1, mathAction, num2, sampleCallback);
That invokes the service and results are populated in the callback object.
getResult is the method exposed by the service so service.getResult(..) populates SampleCallBack
and SampleCallBack populates the textfield:
private TextBox tb.setValue(result);
A reference of Samplecallback is passed to service.getResult and it gets populated with server
side data.
The important thing with AJAX is that it enhances user experience so you can invoke service on
tab-out, tab-in etc don’t have to wait until button click.
Finally you would need to modify the system generated html file, SampleProject.html with the following lines, to successfully generate the GWT based UI.

<link type="text/css" rel="stylesheet" href="SampleProject.css">
<title>Web Application Starter Project</title>
<script type="text/javascript" language="javascript"
    src="sampleproject/sampleproject.nocache.js"></script>
</head>

Note: In order to run GWT on eclipse you may install android dev toolkit (ADT)plugin and then google web toolkit (GWT) plugin that are available on the google site.

Project Heirarchy below:

package com.mattiz.sample.client.entrypoint;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.mattiz.sample.client.model.CalculationCounter;
import com.mattiz.sample.client.service.SampleCallback;
import com.mattiz.sample.client.service.SampleService;
import com.mattiz.sample.client.service.SampleServiceAsync;

public class CalculatorServer implements EntryPoint {

	private TextBox tb = new TextBox();

	public void onModuleLoad() {
		Button button0 = new Button("0");
		button0.setSize("75", "75");
		Button button1 = new Button("1");
		button1.setSize("75", "75");
		Button button2 = new Button("2");
		button2.setSize("75", "75");
		Button button3 = new Button("3");
		button3.setSize("75", "75");
		Button button4 = new Button("4");
		button4.setSize("75", "75");
		Button button5 = new Button("5");
		button5.setSize("75", "75");
		Button button6 = new Button("6");
		button6.setSize("75", "75");
		Button button7 = new Button("7");
		button7.setSize("75", "75");
		Button button8 = new Button("8");
		button8.setSize("75", "75");
		Button button9 = new Button("9");
		button9.setSize("75", "75");
		Button button10 = new Button("+");
		button10.setSize("75", "75");
		Button button11 = new Button("-");
		button11.setSize("75", "75");

		button0.addStyleName("calc-btn");
		button1.addStyleName("calc-btn");
		button2.addStyleName("calc-btn");
		button3.addStyleName("calc-btn");
		button4.addStyleName("calc-btn");
		button5.addStyleName("calc-btn");
		button6.addStyleName("calc-btn");
		button7.addStyleName("calc-btn");
		button8.addStyleName("calc-btn");
		button9.addStyleName("calc-btn");

		HorizontalPanel hPanel1 = new HorizontalPanel();
		hPanel1.setWidth("30%");
		hPanel1.setHorizontalAlignment(HorizontalPanel.ALIGN_LEFT);
		hPanel1.add(button1);
		hPanel1.add(button2);
		hPanel1.add(button3);

		HorizontalPanel hPanel2 = new HorizontalPanel();
		hPanel2.setWidth("30%");
		hPanel2.setHorizontalAlignment(HorizontalPanel.ALIGN_LEFT);
		hPanel2.add(button4);
		hPanel2.add(button5);
		hPanel2.add(button6);

		HorizontalPanel hPanel3 = new HorizontalPanel();
		hPanel3.setWidth("30%");
		hPanel3.setHorizontalAlignment(HorizontalPanel.ALIGN_LEFT);
		hPanel3.add(button7);
		hPanel3.add(button8);
		hPanel3.add(button9);

		HorizontalPanel hPanel4 = new HorizontalPanel();
		hPanel4.setWidth("30%");
		hPanel4.setHorizontalAlignment(HorizontalPanel.ALIGN_LEFT);
		hPanel4.add(button0);
		hPanel4.add(button10);
		hPanel4.add(button11);
		HorizontalPanel hPanel5 = new HorizontalPanel();
		tb.setSize("60", "25");
		hPanel5.add(tb);

		RootPanel.get().add(hPanel1);
		RootPanel.get().add(hPanel2);
		RootPanel.get().add(hPanel3);
		RootPanel.get().add(hPanel4);
		RootPanel.get().add(hPanel5);

		final CalculationCounter CalculationCounter = new CalculationCounter();
		button0.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				if (CalculationCounter.getNum1() == null) {
					CalculationCounter.setNum1("0");
				} else if (CalculationCounter.getNum2() == null) {
					CalculationCounter.setNum2("0");
					getValueFromService(CalculationCounter.getNum1(),
							CalculationCounter.getMathOperation(), CalculationCounter.getNum2());
					CalculationCounter.reset();
				}
			}
		});
		button1.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				if (CalculationCounter.getNum1() == null) {
					CalculationCounter.setNum1("1");
				} else if (CalculationCounter.getNum2() == null) {
					CalculationCounter.setNum2("1");
					getValueFromService(CalculationCounter.getNum1(),
							CalculationCounter.getMathOperation(), CalculationCounter.getNum2());
					CalculationCounter.reset();
				}
			}
		});
		button2.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				if (CalculationCounter.getNum1() == null) {
					CalculationCounter.setNum1("2");
				} else if (CalculationCounter.getNum2() == null) {
					CalculationCounter.setNum2("2");
					getValueFromService(CalculationCounter.getNum1(),
							CalculationCounter.getMathOperation(), CalculationCounter.getNum2());
					CalculationCounter.reset();
				}
			}
		});
		button3.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				if (CalculationCounter.getNum1() == null) {
					CalculationCounter.setNum1("3");
				} else if (CalculationCounter.getNum2() == null) {
					CalculationCounter.setNum2("3");
					getValueFromService(CalculationCounter.getNum1(),
							CalculationCounter.getMathOperation(), CalculationCounter.getNum2());
					CalculationCounter.reset();
				}
			}
		});
		button4.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				if (CalculationCounter.getNum1() == null) {
					CalculationCounter.setNum1("4");
				} else if (CalculationCounter.getNum2() == null) {
					CalculationCounter.setNum2("4");
					getValueFromService(CalculationCounter.getNum1(),
							CalculationCounter.getMathOperation(), CalculationCounter.getNum2());
					CalculationCounter.reset();
				}
			}
		});
		button5.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				if (CalculationCounter.getNum1() == null) {
					CalculationCounter.setNum1("5");
				} else if (CalculationCounter.getNum2() == null) {
					CalculationCounter.setNum2("5");
					getValueFromService(CalculationCounter.getNum1(),
							CalculationCounter.getMathOperation(), CalculationCounter.getNum2());
					CalculationCounter.reset();
				}
			}
		});
		button6.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				if (CalculationCounter.getNum1() == null) {
					CalculationCounter.setNum1("6");
				} else if (CalculationCounter.getNum2() == null) {
					CalculationCounter.setNum2("6");
					getValueFromService(CalculationCounter.getNum1(),
							CalculationCounter.getMathOperation(), CalculationCounter.getNum2());
					CalculationCounter.reset();
				}
			}
		});
		button7.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				if (CalculationCounter.getNum1() == null) {
					CalculationCounter.setNum1("7");
				} else if (CalculationCounter.getNum2() == null) {
					CalculationCounter.setNum2("7");
					getValueFromService(CalculationCounter.getNum1(),
							CalculationCounter.getMathOperation(), CalculationCounter.getNum2());
					CalculationCounter.reset();
				}
			}
		});
		button8.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				if (CalculationCounter.getNum1() == null) {
					CalculationCounter.setNum1("8");
				} else if (CalculationCounter.getNum2() == null) {
					CalculationCounter.setNum2("8");
					getValueFromService(CalculationCounter.getNum1(),
							CalculationCounter.getMathOperation(), CalculationCounter.getNum2());
					CalculationCounter.reset();
				}
			}
		});
		button9.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				if (CalculationCounter.getNum1() == null) {
					CalculationCounter.setNum1("9");
				} else if (CalculationCounter.getNum2() == null) {
					CalculationCounter.setNum2("9");
					getValueFromService(CalculationCounter.getNum1(),
							CalculationCounter.getMathOperation(), CalculationCounter.getNum2());
					CalculationCounter.reset();
				}
			}
		});
		button10.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				CalculationCounter.setMathOperation("+");
			}
		});
		button11.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
					CalculationCounter.setMathOperation("-");
			}
		});
	}

	private void getValueFromService(String num1, String mathAction, String num2) {
		SampleServiceAsync service = (SampleServiceAsync) GWT
				.create(SampleService.class);
		ServiceDefTarget serviceDef = (ServiceDefTarget) service;
		serviceDef.setServiceEntryPoint(GWT.getModuleBaseURL() + "userService");
		SampleCallback sampleCallback = new SampleCallback(tb);
		service.getResult(num1, mathAction, num2, sampleCallback);
	}
}
package com.mattiz.sample.client.service;

import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;


//
@RemoteServiceRelativePath("userService")
public interface SampleService extends RemoteService {

	public String getResult(String num1, String mathAction, String num2);
}

package com.mattiz.sample.server;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.mattiz.sample.client.service.SampleService;

public class SampleServiceImpl extends RemoteServiceServlet implements
		SampleService {

	private static final long serialVersionUID = 1L;

	public String getResult(String num1, String mathAction, String num2) {
		int numberOne = Integer.parseInt(num1);
		int numberTwo = Integer.parseInt(num2);
		if (mathAction.equals("+")) {
			return (numberOne + numberTwo) + "";
		} else if (mathAction.equals("-")) {
			return (numberOne - numberTwo) + "";
		} else
			throw new RuntimeException("INVALID ENTRY");
	}

}

package com.mattiz.sample.client.service;

import com.google.gwt.user.client.rpc.AsyncCallback;

public interface SampleServiceAsync {

	void getResult(String num1, String mathAction, String num3, AsyncCallback<String> callback);
}

package com.mattiz.sample.client.service;

import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.TextBox;


public class SampleCallback implements AsyncCallback<String> {
	private TextBox tb;

	public SampleCallback(TextBox tb) {
		this.tb = tb;
	}

	public void onFailure(Throwable caught) {
		Window.alert(caught.getMessage());
	}

	public void onSuccess(String result) {
		tb.setValue(result);
	}

}

package com.mattiz.sample.client.model;

import java.io.Serializable;

public class CalculationCounter implements Serializable {
	private  final long serialVersionUID = 5962982131382782218L;

	private  String num1;
	private  String num2;
	private  String mathOperation;
	private  int counter;

	public  String getNum1() {
		return num1;
	}

	public  void setNum1(String num1) {
		this.num1 = num1;
	}

	public  String getNum2() {
		return num2;
	}

	public  void setNum2(String num2) {
		this.num2 = num2;
	}

	public  String getMathOperation() {
		return mathOperation;
	}

	public  void setMathOperation(String mathOperation) {
		this.mathOperation = mathOperation;
	}

	public  int getCounter() {
		return counter;
	}

	public  void setCounter(int counter) {
		this.counter = counter;
	}

	public  void reset() {
		num1 = null;
		num2 = null;
		mathOperation = null;
	}
}

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee  http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"    version="2.5" xmlns="http://java.sun.com/xml/ns/javaee">
  <!-- Default page to serve -->
  <welcome-file-list>
    <welcome-file>SampleProject.html</welcome-file>
  </welcome-file-list>
   
  <servlet>
    <servlet-name>userServlet</servlet-name>
    <servlet-class>com.mattiz.sample.server.SampleServiceImpl</servlet-class>
  </servlet>
   
  <servlet-mapping>
    <servlet-name>userServlet</servlet-name>
    <url-pattern>/sampleproject/userService</url-pattern>
  </servlet-mapping>
</web-app>

SampleProject.gwt.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 1.6.4//EN" "http://google-web-toolkit.googlecode.com/svn/tags/1.6.4/distro-source/core/src/gwt-module.dtd">
<module rename-to='SampleProject'>
    <inherits name='com.google.gwt.user.User' />
    <inherits name='com.google.gwt.user.theme.standard.Standard' />
 
    <!-- Specify the app entry point class. -->
    <entry-point class='com.mattiz.sample.client.entrypoint.CalculatorServer' />
</module>

SampleProject.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link type="text/css" rel="stylesheet" href="SampleProject.css">
<title>Web Application Starter Project</title>
 
<script type="text/javascript" language="javascript"
    src="sampleproject/sampleproject.nocache.js"></script>
</head>
<body>
</body>
</html>

Add these lines to the default css

.calc-btn {
	height: 5.7em;
	margin-bottom: 5px;
	padding-bottom: 3px;
	font-size: 12px;
	font-family: arial, sans-serif;
}

About cuppajavamattiz
Matty Jacob - Avid technical blogger with interests in J2EE, Web Application Servers, Web frameworks, Open source libraries, Relational Databases, Web Services, Source control repositories, ETL, IDE Tools and related technologies.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: