This article is based on GWT IN Action, Second Edition, published on May 2011. It is being reproduced here by permission from Manning Publications. Manning publishes MEAP (Manning Early Access Program,) eBooks and pBooks. MEAPs are sold exclusively through Manning.com. All pBook purchases include free PDF, mobi and epub. When mobile formats become available all customers will be contacted and upgraded. Visit Manning.com for more information. [ Use promotional code ‘java40beat’ and get 40% discount on eBooks and pBooks ]
Introduction
With GWT v2, it is going to be a rare occasion where you need to create a widget directly from the Document Object Model (DOM). This is because GWT 2.0 covers nearly all the widgets we can think of. If you are using a GWT version lower than 2.0 then you may find some things missing (such as a widget wrapping the SPAN element—GWT 2.0 provides InlineLabel, which does that).
We will look a little into the future and show how to implement a Canvas widget. (You can tell GWT is quite swift moving because GWT 2.2 now provides this type of widget, but we will stick to creating it ourselves to explore some of the points related to creating a widget from the DOM.) A canvas element in HTML allows us to draw two- dimensional objects and bitmaps on the browser screen. Figure 1 shows an example of our Canvas widget in action as it draws a square on the screen.
Listing 1 An example Canvas widget
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | package org.gwtbook.client; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.Widget; public class Canvas extends Widget { #1 public Canvas() { #2 Element element = Document.get().createElement("canvas"); setElement(element); setStyleName("canvas-style"); context = getCanvasContext(element); } protected Canvas(Element element) { #3 setElement(element); assert element.getTagName().equalsIgnoreCase("canvas"); } public static Canvas wrap(Element element) { #4 assert Document.get().getBody().isOrHasChild(element); Canvas canvas = new Canvas(element); canvas.onAttach(); RootPanel.detachOnWindowClose(canvas); canvas.context = canvas.getCanvasContext(element); return canvas; } JavaScriptObject context; public native void fillRect(int top, int left, int width, int height)/*-{ this.@org.gwtbook.client.Canvas::context.fillRect(top,left, width,height); }-*/; public native void fillStyle(String fillStyle)/*-{ this.@org.gwtbook.client.Canvas::context.fillStyle = fillStyle; }-*/; protected native JavaScriptObject getCanvasContext (Element element)/*-{ #5 return element.getContext('2d'); }-*/; } #1 Extending the Widget class #2 Creating a new Widget #3 Private constructor #4 Wrapping an existing canvas element |
Widgets will extend the Widget class (#1). Most GWT widgets contain two ways to create them: a public constructor used for creating a new widget (#2) from scratch and a protected constructor that creates a widget from an existing element in the DOM (#3).
When creating a new widget, there are generally three steps. First, we create the DOM element. At #2, we use Document.get().createElement(”canvas”)) to create a canvas element. Then, we call the Widget class’s setElement method to bind the element to the widget. After this, we set a style name for the widget. For this widget, we chose the base style name canvas-style.
Typically, a widget would now be complete and ready for use. In our case, we need to take an extra step to get the canvas’s 2D context which is used in later drawing methods. We do that by calling getCanvasContext method, defined at #5 using JSNI.
With everything defined, creating the square in figure 1 in a browser that supports canvas can be created using:
1 2 3 4 5 | Canvas c = new Canvas(); c.setPixelSize(400, 400); RootPanel.get().add(c); c.fillStyle("rgb(80,255,80)"); c.fillRect(10, 20, 100, 50); |
This code creates a new Canvas widget instance, sets its size, adds it to the RootPanel, and then uses two specific methods we defined in the widget to set the fill style and draw the rectangle.
If we had the following HTML in the body of our application’s HTML file
1 | <canvas id="demo-canvas" width="400" height="400"></canvas> |
Then we could use the wrap method to create the widget. We get the same result as figure 1 using the following code instead of the code above.
1 2 3 | Canvas c = Canvas.wrap(RootPanel.get("demo-canvas").getElement()); c.fillStyle("rgb(80,255,80)"); c.fillRect(10, 20, 100, 50); |
The wrap method in listing 1 (#4) is copied here.
1 2 3 4 5 6 7 | public static Canvas wrap(Element element) { assert Document.get().getBody().isOrHasChild(element); #1 Canvas canvas = new Canvas(element); #2 canvas.onAttach(); #3 RootPanel.detachOnWindowClose(canvas); #3 return canvas; } |
It first asserts the element passed as the argument is attached to the DOM (#1). If it is, then it calls the protected Canvas constructor (#2), which binds the element to the widget and asserts that it is indeed a canvas element. Once the widget is constructed, we need to manually call two housekeeping methods that GWT would normally call for us (#3).
Because the widget is already attached to the DOM, then we need to call the widget’s onAttach method ourselves to link it into GWT cradle. We also need to manually register the widget with the RootPanel’s detachOnWindowClose method so it is removed safely if the browser window is closed.
Our widget is effectively complete. You could extend it further to implement other canvas functionality, and perhaps use some deferred binding to cope with Internet Explorer’s lack of canvas implementation. We should, though, look at two other things—the functionality that the widget declared will implement and how events are handled.
Indicating functionality
There are a number of Java interfaces that a widget can implement to tell the world it has certain functionality. You don’t need to make your widget implement any of these interfaces, but it does help to adhere to the GWT style, and do so.
The HasText interface indicates the widget has some text, and that we can expect to be able to call the setText method to change that text and the getText method to retrieve the text value.
When you create a new widget, you should consider what interfaces the widget should implement. Some of these are shown in table 1 along with an indication of which package they are in to help you find others.
Table 1 An overview of some of the interfaces a widget can implement to provide indication of functionality.
For our canvas widget, there are no GWT interfaces we need to implement but, next, we’ll hook up an event and so use the HasMouseOverHandler interface.
Hooking up events
Hooking up events is simple. We introduce the appropriate HasXXXXXHandler interface, and then provide an addXXXXXHandler method that calls the addDOMHandler method. (Since our widget extends Widget, this is available to us.)
To show this in practice, let’s add the ability to handle mouse over events to our Canvas widget (see listing 2).
Listing 2 An example Canvas widget
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 28 29 30 31 32 33 34 35 | package com.gwtbook.client.ui; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.Widget; public class Canvas extends Widget implements HasMouseOverHandler { #1 public HandlerRegistration |#2 addMouseOverHandler(MouseOverHandler handler){ |#2 return this.addDomHandler(handler, MouseOverEvent.getType()); #3 } public Canvas() { : See listing 1 for rest of Canvas code : } #1 Creating a new Widget #2 Defining the BookBar class #3 Wrapping an existing canvas element Now, if we run our example code with a little addition— final Canvas c = new Canvas(); c.setPixelSize(400, 400); RootPanel.get().add(c); c.fillStyle("rgb(80,255,80)"); c.fillRect(10, 20, 100, 50); c.addMouseOverHandler(new MouseOverHandler(){ public void onMouseOver(MouseOverEvent event){ c.getElement().getStyle().setBackgroundColor(”lightblue”); } }); |
And put the mouse over the widget, we get figure 2 as the result.
Summary
We created a widget from scratch to represent a DOM element. We mentioned that it is unlikely you will have to do this because GWT already provides widgets for most DOM elements already.