Friday, February 14, 2014

GWT - i18n made easy

Let's start by creating the good old java properties file, for now let's say we want to support English and Chinese.


Right click the String.properties file, then "Open"


However this properties file can not be directly accessed by the client code, as there is no file system when the complied javascript is running in the browser. GWT makes use of the deferred binding to solve this problem.

We need some interface to represent the keys, and let the GWT compiler/runtime decide how to access the properties file and to decide which language to load. Since the properties to interface translation is pretty mechanical, we could let the tool to do that for us. .
<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>gwt-maven-plugin</artifactId>
    <version>${gwt.version}</version>
    <configuration>
        <runTarget>index.html</runTarget>
        <skip>${gwt.skip}</skip>
        <module>${gwt.module}</module>
        <i18nConstantsBundles>
            <i18nConstantsBundle>com.verydapeng.i18n.client.Strings</i18nConstantsBundle>
        </i18nConstantsBundles>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>i18n</goal>
                <goal>compile</goal>
            </goals>
        </execution>
    </executions>
</plugin>
notice we have added a new <i18nConstantsBundles> tag to tell gwt-maven-plugin which properties file to translate. We also need to add a new maven goal <goal>i18n</goal>.

add a custom goal to netbeans gwt:i18n, and run it. we shall see the generated interface.

we also need to modify the way to fire up the dev mode


to access the interface, call the might GWT.create

Strings strings = GWT.<Strings>create(Strings.class);

GWT.log(strings.username());
GWT.log(strings.password());
The bad news is every time a new property is added, you need to run the gwt:i18n goal again (changing the value of a property does not need to do so). The good news is the dev mode can pick up the new property on the fly. Just press F5.

using it inside uibinder will be even more fun
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder 
    xmlns:ui='urn:ui:com.google.gwt.uibinder' 
    xmlns:g='urn:import:com.google.gwt.user.client.ui'>
    
    <ui:with field='strings' type='com.verydapeng.i18n.client.Strings'/>
    
    <g:HTMLPanel>
        <table>
            <tr>
                <td><ui:text from='{strings.username}' /></td>
                <td><g:TextBox /></td>
            </tr>
            <tr>
                <td><ui:text from='{strings.password}' /></td>
                <td><g:PasswordTextBox /></td>
            </tr>
        </table>
    </g:HTMLPanel>

</ui:UiBinder>
and here is the result
now we need to switch to Chinese mode. add the following code to the prod.gwt.xml to tell gwt compiler that we would like to use Chinese.

<extend-property name="locale" values="zh"/>
now restart the GWT dev mode and change the url from
  http://127.0.0.1:8888/index.html?gwt.codesvr=127.0.0.1:9997
to
  http://127.0.0.1:8888/index.html?gwt.codesvr=127.0.0.1:9997&locale=zh
and voila, the Chinese characters come out.

There is more than one way to inform the compiled GWT codes which locale to pickup. One way is to use the locale url parameter as shown above.

You can also use a meta tag to do this.
<head>
    <meta name="gwt:property" content="locale=zh">
</head>
If neither the url parameter nor the meta is desirable, then we can also make use of cookie. Just tell the GWT compiler which cookie name is designated for locale tracking.
<set-configuration-property name="locale.cookie" value="lang"/>
each methods has is own pro and cons, do evaluate all of them and pick the best match.

Code samples are available from https://github.com/verydapeng/gwt-tutorials under the i18n folder

To read more about GWT locale handling, go to http://www.gwtproject.org/doc/latest/DevGuideI18nLocale.html

Friday, February 7, 2014

GWT - UiBinder Quick Intro

Before we start the UiBinder business, let's rewind the clock to GWT 1.x era. This is how to create a simple "click me" button.
public class Entry implements EntryPoint {

    @Override
    public void onModuleLoad() {
        Button button = new Button();
        button.setText("click me");
        button.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                Window.alert("hello world");
            }
        });

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

Thanks to java's verbosity, the signal to noise ratio is pretty low. It goes wild quite fast when we need to express nested DOM structures, like
public class Entry implements EntryPoint {

    @Override
    public void onModuleLoad() {
        Button button = new Button();
        // ... some init code
        Panel outterContainer = new FlowPanel();
        // ... some init codes
        Panel innerContainer = new FlowPanel();
        // ... some init codes
        
        outterContainer.add(innerContainer);
        innerContainer.add(button);

        RootPanel.get().add(outterContainer);
    }
}
Another problem here is, since java code is kind of linear, little aid is provided by the editor to present that nesting structure.

So the xml came to save the world, and we moved to the GWT 2.x era. The basic idea of UiBinder is to express the dom in xml instead of java.
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder 
    xmlns:ui='urn:ui:com.google.gwt.uibinder' 
    xmlns:g='urn:import:com.google.gwt.user.client.ui'>
    <g:FlowPanel>
        <g:Button>hello world</g:Button>
    </g:FlowPanel>
</ui:UiBinder>
The above code will be translated by GWT compiler as
FlowPanel panel = new FlowPanel();

Button button = new Button();
button.setText("hello world");

panel.add(button);
To programmatically access the generated component, we need to give an identifier in the xml
<g:Button ui:field='myButton'>hello world</g:Button>
Now let's move to the java code, the key here is we are going to declare a seemingly orphan interface, then obtaining an instance of it via the might GWT.create() method.
public class Entry implements EntryPoint {

    interface UI extends UiBinder<FlowPanel, Entry> {}

    @Override
    public void onModuleLoad() {
        UI uiBinder = GWT.create(UI.class);        
    }
}
Then using the @UiField annotation to link to the button.
public class Entry implements EntryPoint {

    interface UI extends UiBinder<FlowPanel, Entry> {}

    @UiField Button myButton;
    
    @Override
    public void onModuleLoad() {
        UI uiBinder = GWT.create(UI.class);
        uiBinder.createAndBindUi(this);
        
        Window.alert(myButton.getText());
        RootPanel.get().add(myButton);
    }
}
Now run the project, you will see a boring alert once the app is loaded.

But wait! UiBinder can do more than that, it can be used to hook events. Things to note here,
  1. @UiHandler annotation, it's value needs to be the same as the java field name (myButton)
  2. the annotated method has no return value, void. 
  3. the annotated method takes exactly one argument, which needs to be of some GwtEvent. here ClickEvent is a sub class of GwtEvent
  4. the field must have corresponding addXXXHandler()
  5. the annotated method name as well as the argument name can be anything 
Here is a sample declaration
@UiHandler("myButton")
void onButtonClick(ClickEvent e) {
    myButton.setText("hello event");
}
Now refresh the page and click the button to see its text changed.

Codes are available from https://github.com/verydapeng/gwt-tutorials under the folder UiBinder-Quick-Intro

To dig more out of uibinder, follow the link here http://www.gwtproject.org/doc/latest/DevGuideUiBinder.html

Wednesday, February 5, 2014

GWT - maven based project setup

Let's start with an empty maven web project.



Time to config the pom.xml

<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>com.verydapeng</groupId>
    <artifactId>Project-Setup</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>Project-Setup</name>

    <properties>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
        <failOnMissingWebXml>false</failOnMissingWebXml>        
        <gwt.version>2.6.0-rc3</gwt.version>
        <gwt.module>com.verydapeng.project.setup.prod</gwt.module>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    
    <dependencies>
        <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>
                    <runTarget>index.html</runTarget>
                    <skip>${gwt.skip}</skip>
                    <module>${gwt.module}</module>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>
create the gwt module xml, under the package com.verydapeng.project.setup. let's call it prod.gwt.xml
<module rename-to="app">
    <inherits name="com.google.gwt.user.User" />
    <entry-point class="com.verydapeng.project.setup.client.Entry" />
</module>
create the Entry class
package com.verydapeng.project.setup.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.Window;

public class Entry implements EntryPoint {

    @Override
    public void onModuleLoad() {
        Window.alert("hello world");
    }
}

create the index.html

<!DOCTYPE html>
<html>
    <body>
        <script src="app/app.nocache.js"></script>
    </body>
</html>

Now we are almost done. The project view should be something like this now



Time to run the project!

create a custom action to run the gwt:run goal



wait the GWT dev mode to show up, click the Launch Default Browser button


and viola, Hello World. You may need to install the GWT plugin, if you haven't done so.


sample codes are available here https://github.com/verydapeng/gwt-tutorials under the Project-Setup folder

Tuesday, February 4, 2014

GWT - UiBinder and CSS

The main problem with CSS is the lack of namespace, everything is mashed together into the same global namespace. This is handy for prototyping, however once more and more developers go into the same project, things starts to become tricky. If I want to have a class
.enabled {color:red;}
another developer working on some other component also writing
.enabled {color:blue;}
then we are in trouble

There are several techniques to help mitigate the problem. Learning from C (which has the similar problem). We can prefix everything, instead of
.enabled {color:red;}
we need to write
.myModule-enabled {color:red;}
or
.myModule .enabled {color:red;}
However I feel both of them are less than ideal, prefixing is just a hack around the deficiency of the language, the essence of the codes are about "enabled".

Here is how GWT solves this problem. We still write the naked "enabled" as it is, but the compiler will help us to generate a new class name and replaces the original one.

ComponentA.ui.xml
<ui:style>
    .enabled {color: red;}
</ui:style>

<g:HTML styleName='{style.enabled}'>
    content A
</g:HTML>

ComponentB.ui.xml
<ui:style>
    .enabled {color: blue;}
</ui:style>

<g:HTML styleName='{style.enabled}'>
    content B
</g:HTML>

Notice here, both A and B are using the same naked "enabled" css class name. After GWT compilation, the class names are changed to EI for ComponentA's enabled rule. Now we have two components running happily inside the same page with their preferred css class name.


Source codes are available from https://github.com/verydapeng/gwt-tutorials under /UiBinder-And-CSS/ folder