Showing posts with label google web toolkit. Show all posts
Showing posts with label google web toolkit. Show all posts

Monday, February 8, 2010

GWT games

Many years ago I wrote javascript application that took an image, broke it up into many pieces, then shuffled them into a puzzle for the user. It only worked in I.E. and was horribly written and hard to maintain.

I wanted to revisit this application and was trying to figure out what the best language/framework to use.

I decided that GWT (Google Web Toolkit) would be an excellent choice for this application.

To see the application go here: http://imageoverflow.appspot.com/puzzle.html?imageUrl=http://imageoverflow.appspot.com/get/3b20793dfc300d058ad04d411da152eb?f=t

Now to the code...

The code consists of the following main classes.

ImagePuzzleWidget
PuzzleGrid
ClickableImage

ImagePuzzleWidget

The ImagePuzzleWidget is the main widget that encapsulates the game. It takes no arguments and is added directly to the Root Panel in the GWT entry point.


public void onModuleLoad() {
RootPanel.get().add(new ImagePuzzleWidget());
}

PuzzleGrid

PuzzleGrid extends the Grid GWT component. Its constructor looks like this:

public PuzzleGrid(final String url,final int cols,final int rows)

This class takes the url of the image you want to scramble as well as how many rows and cols you want.

We add an onLoadHandler that will get executed after the image has been loaded:

image.addLoadHandler(new LoadHandler() {

public void onLoad(LoadEvent event) {
setWidth(image.getWidth() + "px");
setHeight(image.getHeight() + "px");


int blockWidth = image.getWidth() / cols;
int blockHeight = image.getHeight() / rows;
for (int col = 0; col <>
for (int row = 0; row <>
setWidget(row, col,
new ClickableImage(url, blockWidth * col, blockHeight * row, blockWidth, blockHeight, row, col));
}
}
shuffle();

setVisible(true);
}
});

It is this class that does most of the work by splitting the image into sub parts and making ClickableImages out of them.

The class then shuffles the images and sets it as visible.


So now we have an image displayed broken into sub parts and displayed in a grid fashion.


ClickableImage

ClickableImage class, as its name implies embodies a section of the image that can be clicked.

The class basically just extends Image and adds a ClickHandler. I add some extra convenience methods like select and unselect which I won't show for the sake of brevity.

public ClickableImage(String url, int left, int top, int width, int height, int row, int col) {
super(url, left, top, width, height);
this.row = row;
this.col = col;
this.addStyleName("unselected");
this.addClickHandler(new ClickHandler() {

public void onClick(ClickEvent event) {
toggleSelected();

}
});
}


And that is really all there is to it. By using GWT we can focus on writing our OO style code and not worry about the browser.

Make sure you check out the app at: http://imageoverflow.appspot.com/puzzle.html?imageUrl=http://imageoverflow.appspot.com/get/3b20793dfc300d058ad04d411da152eb?f=t

This post was written hastily, if there is any interest or question leave a comment and I'll ake the time to expand.

HTH

Friday, September 12, 2008

Google Web Toolkit - Monthly Payment Calculator

This example displays a simple Loan Calculator. When the user clicks calculate a ClickListener is fired which calculates the monthly payment and dispays it.

This example illustrates GWT Labels, TextBox, and Button controls.


/*
* mcintoshEntryPoint.java
*
* Created on June 29, 2008, 1:46 PM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
package com.client;

import com.google.gwt.core.client.EntryPoint;
import com.client.time.*;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;

/**
*
* @author avalanche
*/
public class mcintoshEntryPoint implements EntryPoint {

final Label lblLoanAmount = new Label("Loan Amount:");
final TextBox txtLoanAmount = new TextBox();
final Label lblLoanLength = new Label("Loan Length (Month):");
final TextBox txtLoanLength = new TextBox();
final Label lblInterestRate = new Label("Interest Rate (Yearly):");
final TextBox txtInterestRate = new TextBox();
final Button calculate = new Button("Calculate");
final Label lblResult = new Label("Payment per month:");

public mcintoshEntryPoint() {
calculate.addClickListener(new ClickListener() {

public void onClick(Widget arg0) {

//P = A (1 + r ) ^ N /( 1 + r ) ^ N -1
int loanAmount = Integer.parseInt(txtLoanAmount.getText());
int loanLength = Integer.parseInt(txtLoanLength.getText());
double interestRate = Double.parseDouble(txtInterestRate.getText())/1200;
double temp = Math.pow(( 1 + interestRate ) , loanLength);
Double result = new Double((loanAmount * interestRate * temp) / (temp -1));
lblResult.setText(result.toString());
}
});


}

/**
* The entry point method, called automatically by loading a module
* that declares an implementing class as an entry-point
*/
public void onModuleLoad() {
RootPanel.get().add(lblLoanAmount);
RootPanel.get().add(txtLoanAmount);
RootPanel.get().add(lblLoanLength);
RootPanel.get().add(txtLoanLength);
RootPanel.get().add(lblInterestRate);
RootPanel.get().add(txtInterestRate);
RootPanel.get().add(calculate);
RootPanel.get().add(lblResult);

}
}

Thursday, September 11, 2008

Google Web Toolkit Data Grid Example

At work I have a java J2EE application that displays a large table. The report is about 15 MB when rendered. I've been experimenting with GWT and I think it would be a good tool to rewrite this report with.

The main problem with the page is when someone updates a value it has to post back to the server then re-render everything. Using GWT it seems like it would be trivial to reload a single row at a time. Along those lines here is a simple DataGrid example that lets me add/remove rows.

See GWT Simple Example for more information on setting up your environment.


/*
* mcintoshEntryPoint.java
*
* Created on June 29, 2008, 1:46 PM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
package com.client;

import com.google.gwt.core.client.EntryPoint;
import com.client.time.*;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.SourcesTableEvents;
import com.google.gwt.user.client.ui.TableListener;
import com.google.gwt.user.client.ui.Widget;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
*
* @author avalanche
*/
public class mcintoshEntryPoint implements EntryPoint {

final Button addButton = new Button("Add Row");
final Button deleteButton = new Button("Delete Row");
final FlexTable table = new FlexTable();
final Set selectedTableRows = new HashSet();

public mcintoshEntryPoint() {
addButton.addClickListener(new ClickListener() {

public void onClick(Widget arg0) {
table.insertRow(table.getRowCount());
table.addCell(table.getRowCount() - 1);
table.addCell(table.getRowCount() - 1);
table.setText(table.getRowCount() - 1, 1, "foo" + table.getRowCount());
}
});
deleteButton.addClickListener(new ClickListener() {

public void onClick(Widget arg0) {
Object[] array = selectedTableRows.toArray();
Arrays.sort(array);
for(int i=array.length-1;i>-1;i-- ) {
int row = ((Integer)array[i]).intValue();
table.removeRow(row);
selectedTableRows.remove(new Integer(row));
}
}
});
table.addTableListener(new TableListener() {

public void onCellClicked(SourcesTableEvents arg0, int rrow, int col) {
Integer row = new Integer(rrow);
if (selectedTableRows.contains(row)) {
selectedTableRows.remove(row);
table.setText(rrow, 0, "x");
} else {
selectedTableRows.add(row);
table.setText(rrow, 0, "y");
}
}
});


}

/**
* The entry point method, called automatically by loading a module
* that declares an implementing class as an entry-point
*/
public void onModuleLoad() {
RootPanel.get().add(table);
RootPanel.get().add(addButton);
RootPanel.get().add(deleteButton);
}
}

Friday, July 4, 2008

Google Web Toolkit - Suggest Box (RPC)

The Google Web Toolkit provides many advanced features to make a web page come alive. One feature that is used commonly in AJAX webpages is a select box that has context sensitive options.

I have already covered the SuggestBox, this article will focus on retrieving your suggestion through an asynchronous Javascript call. You may want to read the following articles as this article builds off some of those concepts.

* GWT suggest box
* GWT RPC Call

To create an RPC Suggest Box you will need to do the following
* Create a Service to serve your suggestions. This service will take an input and return a collection of data.
* Create an oracle that wraps the calls to the Service

Create Suggestion RPC Service

Your client side Suggestion box will fire an onchange event when the user enters a keystroke. When this happens you want to send over the entered text and instruct the control to suggest only items that match the pattern.

To do this we need to define a Service. As we saw in
GWT RPC Call we need to define two interfaces we'll call them:
  • IQuoteService
  • IQuoteServiceAsync

public interface IQuoteService extends RemoteService {

public static class Util {
public static IQuoteServiceAsync getInstance() {
IQuoteServiceAsync instance=(IQuoteServiceAsync) GWT.create(IQuoteService.class);
ServiceDefTarget target = (ServiceDefTarget) instance;
target.setServiceEntryPoint("/GWT/quote");
return instance;
}
}

public SuggestOracle.Response getQuote(SuggestOracle.Request req);
}

public interface IQuoteServiceAsync {
public void getQuote(SuggestOracle.Request req, AsyncCallback callback);
}

Your Oracle will need to return a class with a com.google.gwt.user.client.ui.SuggestOracle.Suggestion interface. To faciliate this we create a new class called ItemSuggestion.

Note: This class must reside on the client side else you will get serialization errors when you run the app.


public class ItemSuggestion implements IsSerializable, Suggestion {

private String s;
// Required for IsSerializable to work
public ItemSuggestion() {
}

// Convenience method for creation of a suggestion
public ItemSuggestion(String s) {
this.s = s;
}

public String getDisplayString() {
return s;
}

public String getReplacementString() {
return s;
}
} // end inner class ItemSuggestion



Now define the actual service on the server side:
public class RandomQuoteService extends RemoteServiceServlet implements IQuoteService {


public SuggestOracle.Response getQuote(SuggestOracle.Request req) {

// req has request properties that you can use to perform a db search
// or some other query. Then populate the suggestions up to req.getLimit() and
// return in a SuggestOracle.Response object.
SuggestOracle.Response resp = new SuggestOracle.Response();

List<suggestion> suggestions = new ArrayList<suggestion>();
suggestions.add(new ItemSuggestion("It is a good day to die"));
suggestions.add(new ItemSuggestion("I shall return"));
suggestions.add(new ItemSuggestion("There is nothing to fear but fear itself"));

resp.setSuggestions(suggestions);
return resp;
}
}
Create Suggestion Oracle

So far we've created our service now we need to create the Oracle.

You simply extend SuggestOracle and instruct it to use the Service for its suggestions.

public class ItemSuggestOracle extends SuggestOracle {

public boolean isDisplayStringHTML() {
return true;
}

public void requestSuggestions(SuggestOracle.Request req,SuggestOracle.Callback callback) {
IQuoteService.Util.getInstance().getQuote(req, new ItemSuggestCallback(req, callback));
}

class ItemSuggestCallback implements AsyncCallback {

private SuggestOracle.Request req;
private SuggestOracle.Callback callback;

public ItemSuggestCallback(SuggestOracle.Request _req,
SuggestOracle.Callback _callback) {
req = _req;
callback = _callback;
}

public void onFailure(Throwable error) {
callback.onSuggestionsReady(req, new SuggestOracle.Response());
}

public void onSuccess(Object retValue) {
callback.onSuggestionsReady(req,
(SuggestOracle.Response) retValue);
}
}
}


And now the result of all our work, we create a new SuggestBox with the new oracle.

public void display() {

ItemSuggestOracle oracle = new ItemSuggestOracle();
SuggestBox sb = new SuggestBox(oracle);

// Add it to the root panel.
RootPanel.get().add(sb);
}


That's it! It looks like its a lot of work but its mostly plumbing and you should find its pretty easy to do once you get the hang of it.

Give it a try and leave a comment to let me know how it goes.

Thursday, July 3, 2008

Google Web Toolkit - Suggest Box

There seems to be some confusion on the web about whether GWT supports an auto completion widget. I suspect this is because the first version of GWT didn't support it.

However 1.4 sure does, its called SuggestBox. SuggestBox takes a SuggestOracle that contains the data you want and is responsible for returning suggestions.

Here is an example of how to use it:


MultiWordSuggestOracle oracle = new MultiWordSuggestOracle();
oracle.add("foo");
oracle.add("bar");
oracle.add("baz");
oracle.add("toto");
oracle.add("tintin");

SuggestBox sb = new SuggestBox(oracle);

// Add it to the root panel.
RootPanel.get().add(sb);

Google Web Toolkit - RPC Call

This post will document how to make a RPC call using the Google Web Toolkit.

For a more detailed walk through of the Google Web Toolkit I recommend this book: Google Web Toolkit Applications

To sum it up you must create 1 class and 2 interfaces.

1) Service Interface that extends RemoteService and defines the methods for your service
2) Asynchrounous interface which is a similar to the the above except:
** doesn't extend RemoteService
** all method return void
** one extra AsyncCallBack parameter to each defined method
3) Your actual service which extends RemoteServiceServlet and implements your interface created in step (1)
** This should be a servlet and mapped properly in your web.xml

The two interfaces will need to be in the com.ciient package, and the service in the com.server or on someother url on the same host where this will be deployed.

So on to the code.

First the interfaces and classes, following by a sample app that calls it:


package com.client.time;
public interface ITimeService extends RemoteService {
public Date getDate();
}

package com.client.time;
public interface ITimeServiceAsync {
public void getDate(AsyncCallback callback);
}

package com.server.time;
public class TimeService extends RemoteServiceServlet implements ITimeService {

public Date getDate() {
return new Date();
}
}

public void loadDate(final Label label) {
ITimeServiceAsync timeService = (ITimeServiceAsync) GWT.create(ITimeService.class);


ServiceDefTarget endpoint = (ServiceDefTarget) timeService;
endpoint.setServiceEntryPoint("/GWT/TimeService");

AsyncCallback callback = new AsyncCallback() {

public void onSuccess(Object result) {
label.setText(result.toString());
}

public void onFailure(Throwable caught) {
label.setText("failure" + caught.getMessage());
}
};

label.setText("pending");
timeService.getDate(callback);

}

Google Web Toolkit - Simple Example

The Google Web Toolkit (GWT) is a framework that allows for web development using Java. The framework will convert the Java code into html/javascript.

This approach makes developing and debugging feature rich clients much easier than writing javascript yourself.

To get familiar with the framework I wrote a simple getting started app inspired from the GWT docs. It simply creates a button, label and grid.

It adds an event to the button to hide the label. Also adds a TableListener to the grid. I use this to capture cell clicks and popup a dialog.

Reading the docs you can then style this using css with the following attributes:

* .gwt-DialogBox { the outside of the dialog }
* .gwt-DialogBox .Caption { the caption }



/*
* mcintoshEntryPoint.java
*
* Created on June 29, 2008, 1:46 PM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
package com.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTMLTable;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.SourcesTableEvents;
import com.google.gwt.user.client.ui.TableListener;
import com.google.gwt.user.client.ui.Widget;

/**
*
* @author avalanche
*/
public class mcintoshEntryPoint implements EntryPoint {

/** Creates a new instance of mcintoshEntryPoint */
public mcintoshEntryPoint() {
}

private static class MyDialog extends DialogBox {

public MyDialog(String text) {
// Set the dialog box's caption.
setText(text);

// DialogBox is a SimplePanel, so you have to set its widget property to
// whatever you want its contents to be.
Button ok = new Button("OK");
ok.addClickListener(new ClickListener() {

public void onClick(Widget sender) {
MyDialog.this.hide();
}
});
setWidget(ok);
}
}

/**
* The entry point method, called automatically by loading a module
* that declares an implementing class as an entry-point
*/
public void onModuleLoad() {
final Label label = new Label("Hello, Chris!!!");
final Button button = new Button("Click me!");
final HTMLTable table = new Grid(5, 5);
// Put some values in the grid cells.
for (int row = 0; row < 5; ++row) {
for (int col = 0; col < 5; ++col) {
table.setText(row, col, "" + row + ", " + col);
}
}

button.addClickListener(new ClickListener() {

public void onClick(Widget w) {
label.setVisible(!label.isVisible());
}
});

table.addTableListener(new TableListener() {

public void onCellClicked(SourcesTableEvents arg0, int arg1, int arg2) {
new MyDialog("You clicked" + arg1 + "," + arg2).show();
}
});



RootPanel.get().add(button);
RootPanel.get().add(label);
RootPanel.get().add(table);
}
}