Showing posts with label gwt. Show all posts
Showing posts with label gwt. Show all posts

Saturday, January 02, 2010

Scalable Image with GWT2.0 and DataResource

Introduction

Today, i wanted to use an ImageResource combined with an Image widget to display a resizable image. My first approach was not the good one !!! and it’s why i decided to write this note to provide the trick. Hopefully GWT2.0 has the solution !

Using ImageResource

When using ImageResource, GWT2.0 uses a CSS background url property to render the complete image and set automatically the width and height properties with the image original size. You can see the sources and related CSS just below.

image

image

image

Resizing the original image

My objective was to resize the original image. I tried to use the setSize method. When running the application, the image was cropped (0, 0, 400, 300) and not resized.

image

image

The problem with this ImageResource implementation is that a CSS background property is used and CSS does not allow to resize an image background yet.

The DataResource to the rescue !

The DataResource solution does not use the CSS background property but fills the img src property with the base64 encoded image. The image is correctly resized with the setSize() method.

image

image

image

Conclusion

When the objective is to display a simple image with the original size, using ImageResource is the good approach. When an image dimensioning is required, the DataResource is the solution !

del.icio.us Tags:

Tuesday, December 29, 2009

Rounded button, CSS3 and GWT2.0 ClientBundle

Introduction

CSS3 introduces new powerful capabilities for web designers. Instead of merging several sub-images to generate a rounded button, CSS3 allows to define this button with properties.

This tutorial will not only expose the CSS3 instructions to create the  button. The new GWT2.0 ClientBundle facility will be used to create a GWT RoundedButton.

The basic GWT Button will be used with a dedicated CSS3 style provided by a ClientBundle.

Let’s go !

ClientBundle introduction

GWT2.0 introduces this new  functionality bringing the power of combining and optimizing resources into one download for things like text files, CSS, and XML. This means fewer network roundtrips, which in turn can decrease application latency, especially on mobile applications.

More information regarding ClientBundle can be found here.

Step1: Creating the CSS clientBundle

The .iPush style is used to polish the button in the “normal state”. Basic attributes like color, font and positioning are described in this section. As CSS3 webkit implementation is used, you can notice that a \ is added in front of the webkit. without this \, GWT compile will fire an error during compilation.

The .iPush.IBClassic is used to add the rounded button properties. A “templated button image” is required to define the button properties. This image will automatically adapt is size regarding the text contained in the button or the button forced width.

image

@url annotation is used to define the “templated” image required to design the button(lines 182-183).

  • the @url annotation will produce the url CSS instruction
  • the imageUrl1 is the image referenced in the CSS instruction
  • the bigButtonWhite is a reference to a property defined in the ClientBundle java file in charge of locating the image (.png). This java file will be presented just after…

image

If we take a look at the generated CSS with the Chrome developer tools, we can see that the image has been base64 encoded during the GWT compilation !! no additional network access is used to download the image.

image

Step2: Creating the Java ClientBundle

The Resources interface is used to define all the resources (CSS files, images, JavaScript..) that are part of the bundle.

The previous CSS file is referenced by the @Source(“test.css”) annotation, the image location defined in the CSS file is defined in this interface as well.

image

An other interface (MyCssResource) returned by the previous Resources interface is used to access the styles defined in the CSS file.

image

Using the button in the application

GWT.create method retrieves the Resources interface (56).

The button is “styled” with the MyCssResources iPush and iBClassic methods. This 2 methods are related to the .iPush and .iPush.iBClassic defined in the CSS file.

image

Finally, the resource is injected to the component (60)

And the result is:

image

Conclusion

This tutorial was for me the occasion to test the ClientBundle provided by the new GWT2.0 framework. Using ClientBundle seems really a good way to reduce the network transfers and only use the “active” part of the CSS.

Special thanks to Thomas Broyer who provide me efficient support !

del.icio.us Tags: ,,

Monday, May 18, 2009

Google App Engine for Java, GWT and GXT

Introduction

With the launch of the new Google App Engine (Beta) delivered with an Eclipse plugin managing GAE and GWT1.6, it is now very easy to deploy new GWT applications in the Google cloud.
GXT is a java library for building rich Internet applications with GWT. As demonstrated on their Web site, this library provides a huge quantity of graphical components to help you creating applications very quickly.

My objective was to evaluate how easy it is to deploy a basic application combining GAE-1.2.1, GWT-1.6.4 and GXT-2.0-md01.

Steps to create the application

Downloading and installing the GAE SDK-1.2.1

This step is done without any problem.

Installing the Eclipse Plugin for GAE

The Eclipse Plugin for GAE is easy to install as well and adds 3 buttons in the toolbar.
image

The contextual menu is modified as well to add a a Google entry

image

Those 3 buttons are used to:

  • Create a new GAE application. You can choose to use GWT, GAE or both. image
  • Compile the code (transform the Java code in JavaScript)
  • Deploy your application on the Google App Engine servers.

Adding the GXT2.0-md01 jar library

You simply need to add an external java library in the project.

image

Coding the application using GWT and GXT

No remark in this section, no specific think related to GAE.

Compiling and deploying the application

Of course, you need to have a developer account to deploy your application.
Before deploying the application, you need to enter the applicationId obtain on the GAE web portal

image image

After entering this information, you only need to click on the image button from the toolbar. A popup appears asking you to fill your Google password. If all is OK, you should visualize your application on a Web browser.

Remarks and problems

For a basic application, there is really no problem, compilation and deployment is working fine. For a more elaborated application with GXT library, problems can occurs.

StackOverflow error during compilation

You need to click on the Advanced link and add the –Xss16M VM argument.

image

Max number of files and blobs is 1000

Your application can use a lot of static files for images, CSS, and JavaScript. When the number of files is greater than 1000, the deployer fires an error.

image To avoid this problem, you need to reduce the number of static files included in your application.

Multiple options are available:
  • Locating your static file in an other Web server
  • Zipping the static files and serving those zipped files with a Servlet included in your application. This solution is working fine for development process but can not be used in a production solution. A link with a sample code is available ">here.

Other remarks

  • During the tests i need to connect behind a proxy. The Eclipse proxy configuration needs to be used to deploy the GEA with a proxy.
  • GAE can be used with an ant file available here. This build file does not include the target to compile the GWT code. To compile the GWT code with this build, you need to add the gwtc target section.

image

Conclusion

This post explains very quickly the main steps to create an application with GWT, GAE and GXT. The Eclipse Plugin is well designed and the application development is easy. I will try to continue playing with all capabilities provided by the GAE (database, …)

Sunday, May 17, 2009

GeoLocation with Google Gears and GWT

Introduction

Geo-location is used and available everywhere and it exists multiple solutions to retrieve the position.
Computed position is more or less accurate regarding the technology used.

  • Basic handsets can retrieve the location thanks the operator base stations.
  • GPS handsets can retrieve a more accurate position with GPS or AGPS technologies.
  • When GPS signal is to limited and WIFI available, WIFI positioning can be used...

More and more applications are available with location capabilities.
Those applications are used to find the nearest restaurant, cinema.
Big companies are launching new services to locate your friends (Google Latitude)…

To create those applications, a location API is required to get the handset or laptop position.
The main features of those API are:
  • Returning a position (longitude, latitude, speed) taking abstraction of the underlying technologies (GPS, WIFI, BTS...)
  • Returning the response accuracy.
  • Tracking the handset(return multiple location each time a modification is observed)

This article will focus on the location API's available for PCs and laptops Internet browsers(Safari, IE, Firefox) and more especially on the Google Gears technology.

Google Gears

Google is proposing the Gears API for Geo-Location purpose.

This Google API like the majority of other APIs is returning the position BUT can in addition return geocoding information like the country, city, street number, street and region information.
After installing this plugin, web applications have the possibility to retrieve the location from the Internet browser.
A very basic JavaScript API is proposed to fulfill the main location features exposed previously.

For people familiar with the Google Web Toolkit framework, this Gears API is available as a GWT module.

Next part of the article will demonstrate how to use this GWT Gears API.

To create a web application with GWT and Gears, you need:

  • Download the Gears plugin available Here
  • Download the Gears GWT module available Here
  • Create a basic GWT application with Eclipse or other Java editor

The GWT-Gears Java code

Retrieving the GeoLocation service from the Gears factory

image

To retrieve the current location, you can use the getCurrentPosition method. As you can see in the code, options can be used to specify the accuracy.

If you want to retrieve the address information determined from the longitude and latitude, you need to set the gearsRequestAddress to true. By default, the geocoding is disabled.

image

If you want to be notified each time the location is modified, you need to use the watchPosition method. This method is returning an identifier used to stop the tracking.

image

If you want to stop the tracking (started with the watchPosition method), you need to call the clearWatch method.

image

Conclusion

The Google Gears API (available as a JavaScript library or a GWT module) is very easy to use and provides interesting geocoding features to retrieve Address information.

A W3C standard available here is describing an API for location for Web browsers. The Google API seems very closed from this specification.

In a next article, an overview of the IPhone3.0 Safari location API will be exposed.

Saturday, July 26, 2008

GWT-Ext: Wrapping a Google Map component

The aim of this tutorial is to present how to wrap a Google Map component with GWT-Ext.

GWT-Ext proposes a lot of Google Map wrappers. The tutorial proposes to demonstrate how to wrap the “Direction” and “Map” components. Thanks this component it’s now easy to reuse a Direction in a web page.

Direction widget features:

directions

  • Map and direction in the same component
  • Map and directions size are automaticaly adjusted depending component size
  • Optional form to define origin and destination
  • Horizontal or vertical layout

 

Main steps to create the widget:

Overloading the Panel component
Creating the “<Div>” elements used by the Google Map API
Those 2 elements are used by the Google API (next step) to display the map and the directions components.
        String mapDiv = "
";
String directionsDiv = "
";
_mapHTMLPanel = new HTMLPanel(mapDiv);
_directionsHTMLPanel = new HTMLPanel(directionsDiv);



Initializing the Google Map API
GMap2 and GDirections javascript objects are initialized. $wnd is used by GWT API to access javascript objects.
    private native JavaScriptObject initializeMap() /*-{
var map = new $wnd.GMap2($doc.getElementById('widget_map_canvas'));
map.addControl(new $wnd.GSmallMapControl());
map.addControl(new $wnd.GMapTypeControl());
return map;
}-*/;

private native JavaScriptObject initializeDirections(Directions thisModule) /*-{
var map = this.@com.benevolarc.gwt.client.Directions::_map;
var gdir = new $wnd.GDirections(map, $doc.getElementById('widget_directions'));
$wnd.GEvent.addListener(gdir, "load", function(response) {
if (response && response.getStatus().code == 200) {
thisModule.@com.benevolarc.gwt.client.Directions::directionsOK()();
}
});



Creating the layout (horizontal or vertical) to store the Map and the Direction components
The layout allows to display the map and the directions with 2 options (left-rigth or top-down)
Muliple GWT-Ext layouts are used for that purpose:

  1. FitLayout is used a base class for layouts that contain a single item that automatically expands to fill the layout's container

  2. Panels with ColumLayoutData are used for the Horizontal mode

  3. AnchorLayoutData with PanelListenerAdapter is used for the Horizontal mode


The difficulty was to display an horizontal panel with a height expressed in percentage instead of pixel. The solution was to use a PanelListenerAdapter.
    private void init() {
setLayout(new FitLayout());
setBorder(false);
String mapDiv = "
";
String directionsDiv = "
";
_mapHTMLPanel = new HTMLPanel(mapDiv);
_directionsHTMLPanel = new HTMLPanel(directionsDiv);

//_panel.add(new HTMLPanel("
"));

if (_displayMode == DisplayMode.HORIZONTAL) {
Log.debug("Horizontal mode");
Panel columnLayoutPanel = new Panel();
columnLayoutPanel.setBorder(false);
columnLayoutPanel.setLayout(new ColumnLayout());

col1Panel = new Panel();
col1Panel.setBorder(false);
col1Panel.setLayout(new FitLayout());
Panel panelMap = new Panel();
panelMap.setBorder(false);
_mapHTMLPanel.setMargins(10, 10, 10, 10);
panelMap.setLayout(new AnchorLayout());
if (_displayForm) {
panelMap.add(createFromToForm());
}
panelMap.add(_mapHTMLPanel, new AnchorLayoutData("100% 100%"));
col1Panel.add(panelMap);
col2Panel = new Panel();
col2Panel.setBorder(false);
col2Panel.setLayout(new FitLayout());
col2Panel.add(_directionsHTMLPanel);

columnLayoutPanel.add(col1Panel, new ColumnLayoutData(.50));
columnLayoutPanel.add(col2Panel, new ColumnLayoutData(.50));
columnLayoutPanel.addListener(new PanelListenerAdapter() {
public void onResize(BoxComponent component, int adjWidth, int adjHeight, int rawWidth, int rawHeight) {
col1Panel.setHeight(rawHeight);
col2Panel.setHeight(rawHeight);
}
});


add(columnLayoutPanel);

} else {
Log.debug("Vertical mode");
Panel panel = new Panel();
panel.setLayout(new AnchorLayout());
if (_displayForm) {
FormPanel fPanel = createFromToForm();
panel.add(fPanel);
}
panel.add(_mapHTMLPanel, new AnchorLayoutData("100% 50%"));
_mapHTMLPanel.collapse();
panel.add(_directionsHTMLPanel, new AnchorLayoutData("100% 50%"));
add(panel);
}
}



Invoking the directions
When the user clicks on the "get directions" button a call to the Google API has to be done. A listener is attached on the button and a call to the JSNI method is done
        
goButton.addListener(new ButtonListenerAdapter() {
public void onClick(Button button, EventObject e) {
lauchDirections();
}
});

launchDirections checks if origin and destination are correctly filled and call the setDirections native method.
    private void lauchDirections() {
Log.debug("Getting direction" + _map);
if (_toTextField.getText().length() > 0 && _fromTextField.getText().length() > 0) {
setDirections(_fromTextField.getValueAsString(), _toTextField.getValueAsString(), _locale);
clearErrorMessage();
} else {
Log.warn("From and to must be filled");
setErrorMessage("From and to must be filled");
}
}

The setDirections method calls the Google API method gdir.load. In case of success, the Map and the directions components are updated. In case of error, a message is displayed to the user.
  public native void setDirections(String fromAddress, String toAddress, String locale) /*-{
var gdir = this.@com.benevolarc.gwt.client.Directions::_gdir;
gdir.clear();
gdir.load("from: " + fromAddress + " to: " + toAddress,
{ "locale": locale });
}-*/;


Using the Directions component

Using the component is easy

  1. Displaying the Directions and Map component with origin and destination in the constructor (horizontal layout)
    Default component size is 600px*600px

    directions = new Directions("paris","bordeaux", Directions.DisplayMode.HORIZONTAL);


  2. Displaying the Form allowing the user to enter origin and destination (horizontal layout)

    //directions with Horizontal layout
    directions = new Directions(true, Directions.DisplayMode.HORIZONTAL);
    directions.setWidth("1000px");
    directions.setHeight("800px");
    directions.setBorder(true);



  3. Displaying the Form allowing the user to enter origin and destination vertical layout)

    //directions with Horizontal layout
    directions = new Directions(true, Directions.DisplayMode.VERTICAL);
    directions.setBorder(true);




Conclusion

The tutorial demonstrates how to wrap a Google component. Of course this component is not perfect and a lot of improvement needs to be done.

  1. Improve the look and feel

  2. Support to Google street

  3. Internationalization support

  4. Printing capabilities

  5. And more...


Feel free to reuse it and please don't hesitate to give your opinion. If you find this component usefull, the source can be use for GWT-EXT extenstion...
Thanks for GWT-Ext forum for the support regarding layout management.


Complete Code


 
package com.benevolarc.gwt.client;

import com.allen_sauer.gwt.log.client.Log;

import com.google.gwt.core.client.JavaScriptObject;

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

import com.gwtext.client.core.EventObject;
import com.gwtext.client.widgets.BoxComponent;
import com.gwtext.client.widgets.Button;
import com.gwtext.client.widgets.HTMLPanel;
import com.gwtext.client.widgets.Panel;
import com.gwtext.client.widgets.event.ButtonListenerAdapter;
import com.gwtext.client.widgets.event.PanelListenerAdapter;
import com.gwtext.client.widgets.form.Field;
import com.gwtext.client.widgets.form.FormPanel;
import com.gwtext.client.widgets.form.MultiFieldPanel;
import com.gwtext.client.widgets.form.TextField;
import com.gwtext.client.widgets.form.event.FieldListenerAdapter;
import com.gwtext.client.widgets.layout.AnchorLayout;
import com.gwtext.client.widgets.layout.AnchorLayoutData;
import com.gwtext.client.widgets.layout.ColumnLayout;
import com.gwtext.client.widgets.layout.ColumnLayoutData;
import com.gwtext.client.widgets.layout.FitLayout;


public class Directions extends Panel{
//private Panel _panel;
private TextField _fromTextField;
private TextField _toTextField;
private HTMLPanel _mapHTMLPanel;
private HTMLPanel _directionsHTMLPanel;
private JavaScriptObject _map;
private JavaScriptObject _gdir;
public enum DisplayMode {HORIZONTAL, VERTICAL};
private DisplayMode _displayMode = DisplayMode.VERTICAL;
private boolean _displayForm = true;
private Panel col1Panel = null;
private Panel col2Panel = null;
private HTML _errorMessage = new HTML();
private String _from = null;
private String _to = null;
private String _locale = "EN";

public Directions(boolean displayForm) {
_displayForm = displayForm;
init();
}

public Directions(boolean displayForm, DisplayMode displayMode) {
_displayForm = displayForm;
_displayMode = displayMode;
init();
}

public Directions(String from, String to, DisplayMode displayMode) {
_displayMode = displayMode;
_displayForm = false;
_from = from;
_to = to;
init();
}

private FormPanel createFromToForm() {
_fromTextField = new TextField("From", "from", 250);
_toTextField = new TextField("To", "to", 250);
Button goButton = new Button("Get directions");

MultiFieldPanel directionsFormPanel = new MultiFieldPanel();

directionsFormPanel.setBorder(false);
directionsFormPanel.setPaddings(10, 10, 10, 10);
directionsFormPanel.addToRow(_fromTextField, 300);
directionsFormPanel.addToRow(_toTextField, 300);
directionsFormPanel.addToRow(goButton, 100);

FormPanel fPanel = new FormPanel();
fPanel.setBorder(false);
fPanel.setLabelWidth(30);
fPanel.add(_errorMessage);
fPanel.add(directionsFormPanel);
_fromTextField.addListener(new FieldListenerAdapter() {
public void onSpecialKey(Field field, EventObject e) {
if (e.getKey() == EventObject.ENTER) {
lauchDirections();
}
}
});
_toTextField.addListener(new FieldListenerAdapter() {
public void onSpecialKey(Field field, EventObject e) {
if (e.getKey() == EventObject.ENTER) {
lauchDirections();
}
}
});

goButton.addListener(new ButtonListenerAdapter() {
public void onClick(Button button, EventObject e) {
lauchDirections();
}
});
return fPanel;
}

private void setErrorMessage(String message) {
_errorMessage.setHTML("
Error: "+ message + "
");
}

private void clearErrorMessage() {
_errorMessage.setHTML("
");
}

private void lauchDirections() {
Log.debug("Getting direction" + _map);
if (_toTextField.getText().length() > 0 && _fromTextField.getText().length() > 0) {
setDirections(_fromTextField.getValueAsString(), _toTextField.getValueAsString(), _locale);
clearErrorMessage();
} else {
Log.warn("From and to must be filled");
setErrorMessage("From and to must be filled");
}
}

private void init() {
Log.debug("Setting default width and heigth");
setWidth(600);
setHeight(600);
setLayout(new FitLayout());
setBorder(false);
String mapDiv = "
";
String directionsDiv = "
";
_mapHTMLPanel = new HTMLPanel(mapDiv);
_directionsHTMLPanel = new HTMLPanel(directionsDiv);

//_panel.add(new HTMLPanel("
"));

if (_displayMode == DisplayMode.HORIZONTAL) {
Log.debug("Horizontal mode");
Panel columnLayoutPanel = new Panel();
columnLayoutPanel.setBorder(false);
columnLayoutPanel.setLayout(new ColumnLayout());

col1Panel = new Panel();
col1Panel.setBorder(false);
col1Panel.setLayout(new FitLayout());
Panel panelMap = new Panel();
panelMap.setBorder(false);
_mapHTMLPanel.setMargins(10, 10, 10, 10);
panelMap.setLayout(new AnchorLayout());
if (_displayForm) {
panelMap.add(createFromToForm());
}
panelMap.add(_mapHTMLPanel, new AnchorLayoutData("100% 100%"));
col1Panel.add(panelMap);
col2Panel = new Panel();
col2Panel.setBorder(false);
col2Panel.setLayout(new FitLayout());
col2Panel.add(_directionsHTMLPanel);

columnLayoutPanel.add(col1Panel, new ColumnLayoutData(.50));
columnLayoutPanel.add(col2Panel, new ColumnLayoutData(.50));
columnLayoutPanel.addListener(new PanelListenerAdapter() {
public void onResize(BoxComponent component, int adjWidth, int adjHeight, int rawWidth, int rawHeight) {
col1Panel.setHeight(rawHeight);
col2Panel.setHeight(rawHeight);
}
});


add(columnLayoutPanel);

} else {
Log.debug("Vertical mode");
Panel panel = new Panel();
panel.setLayout(new AnchorLayout());
if (_displayForm) {
FormPanel fPanel = createFromToForm();
panel.add(fPanel);
}
panel.add(_mapHTMLPanel, new AnchorLayoutData("100% 50%"));
_mapHTMLPanel.collapse();
panel.add(_directionsHTMLPanel, new AnchorLayoutData("100% 50%"));
add(panel);
}
}

protected void onAttach() {
Log.debug("Component attached");
_map = initializeMap();
_gdir = initializeDirections(this);
if (_from != null && _to != null) {
setDirections(_from, _to, _locale);
} else {
_mapHTMLPanel.collapse();
}
super.onAttach();
}

private native JavaScriptObject initializeMap() /*-{
var map = new $wnd.GMap2($doc.getElementById('widget_map_canvas'));
map.addControl(new $wnd.GSmallMapControl());
map.addControl(new $wnd.GMapTypeControl());
return map;
}-*/;

private native JavaScriptObject initializeDirections(Directions thisModule) /*-{
var map = this.@com.benevolarc.gwt.client.Directions::_map;
var gdir = new $wnd.GDirections(map, $doc.getElementById('widget_directions'));
$wnd.GEvent.addListener(gdir, "load", function(response) {
if (response && response.getStatus().code == 200) {
thisModule.@com.benevolarc.gwt.client.Directions::directionsOK()();
}
});

$wnd.GEvent.addListener(gdir, "error", function(response) {
if (!response || response.getStatus().code != 200) {
thisModule.@com.benevolarc.gwt.client.Directions::directionsError(I)(response.getStatus().code);
}
});
return gdir;
}-*/;

public void directionsError(int error) {
Log.debug("Error");
String errorMessage = "Unknown error";
switch (error) {
case 601: errorMessage = "Missing query";break;
case 602: errorMessage = "Unknown address";break;
case 603: errorMessage = "Unavailable address";break;
case 604: errorMessage = "Unknown direction";break;
default: errorMessage ="Unknown error";
}
setErrorMessage(errorMessage);
_mapHTMLPanel.collapse();
_directionsHTMLPanel.collapse();
}

public void directionsOK() {
Log.debug("Directions OK");
_mapHTMLPanel.expand(true);
_directionsHTMLPanel.expand(true);
}


public native void setDirections(String fromAddress, String toAddress, String locale) /*-{
var gdir = this.@com.benevolarc.gwt.client.Directions::_gdir;
gdir.clear();
gdir.load("from: " + fromAddress + " to: " + toAddress,
{ "locale": locale });
}-*/;

public void setLocale(String locale) {
this._locale = locale;
}

public String getLocale() {
return _locale;
}



}

Tuesday, July 15, 2008

Rounded corners panel with GWT

Native GWT solution

GWT provides a native solution to build panels with rounded corners. This solution is done with a DecoratorPanel component.

The rounded corners are build thanks a .css file customization where images needs to be provided in the css file.

html>body .gwt-DecoratorPanel .topLeft {
  background: url(images/corner.png) no-repeat 0px 0px;
}

The drawback of this solution is that images need to be provided when a new rounded panel has to be created with a new color.

Bouwkamp solution

An other solution without providing any images for corner definition is available thanks http://code.google.com/p/com-bouwkamp-gwt/. By this way creating a new rounded corner panel with different colors is a very basic task.

Steps to create a new rounded panel:

  • Download the jar file and reference this file in the classpath
  • Modify the <appli>.gwt.xml and add :

<inherits name='com.bouwkamp.gwt.user.User' />

  • Create your java file:

    By default the height of the corners is 2px. It is possible to set a different height at construction time. The height can be a value between and including 1 and 9. This value doesn't correspond exactly with the height, e.g. 9 is 12px height.

     // all 4 corners are rounded and height index 5
    RoundedPanel rp = new RoundedPanel(yourWidget, ALL, 5);



    In the previous sample yourWidget can be any component like a VerticalPanel



    You can even define the color of the corner programaticaly:




    // all 4 corners are rounded.
    RoundedPanel rp = new RoundedPanel(yourWidget);
    rp.setCornerColor("red");




  • Customize the .css application file for rounder corner customization


  • Default the css style name of the rounded corner divs is cbg-RP. Use it to set the colors of the corner. For example:




     .cbg-RP { background-color:#c3d9ff; }



    Conclusion



    The solution provided by the Bouwkamp library is very easy to use. Some improvement can be done like providing a rounded corner support and shadow borders. It seems javascript libary is available for this feature see:



    http://www.ruzee.com/blog/shadedborder/






 

Saturday, July 12, 2008

GWT-Ext, Remote ComboBox tutorial

Designing a local comboBox is a basic documented task. For the remote mode (where data to be suggested are returned from a server) the documentation is not so clear.

In this post, a basic review of the LOCAL mode is done and in a second part, the REMOTE mode is developped.

Local comboBox mode:

Basic steps:

  1. Store initialization
  2. Loading the store
  3. Creating the combobox with LOCAL mode
  4. Linking the combobox with the store

Sample code:

        final Store store = 
new SimpleStore(new String[] { "civilite", "desc", },
new String[][] { new String[] { "Mr", "Mr" },
new String[] { "Mlle", "Mlle" },
new String[] { "Mde", "Mde" } });
store.load();

final ComboBox cb = new ComboBox();
cb.setForceSelection(true);
cb.setMinChars(1);
cb.setFieldLabel("Civilité");
cb.setStore(store);
cb.setAllowBlank(false);
cb.setDisplayField("civilite");
cb.setMode(ComboBox.LOCAL);
cb.setTriggerAction(ComboBox.ALL);
cb.setEmptyText("Enter civilité");
cb.setLoadingText("Recherche...");
cb.setTypeAhead(true);
cb.setEditable(false);
cb.setSelectOnFocus(true);
cb.setWidth(230);

cb.setHideTrigger(false);
add(cb);


Remote comboBox mode:


In that case, the store needs to be constructed dynamicaly from the server.

Each time the user tapes a new key in the combobox, a request has to be sent to the server to update the suggest box. The current combobox value has to be sent as a parameter to the server.

The combobox REMOTE mode uses a POST query to the server with the query parameter filled with the current combobox value.

The server side (any servlet or CGI) needs to analyze the POST request, retrieve the query parameter and return an answer to be displayed in the combobox (in the sample code, JSON is used). Some parameters can be set for autocomplete configuration:

setMinChar: The minimum number of characters the user must type before autocomplete activate (default is 4)

setQueryDelay: The length of time in milliseconds to delay between the start of typing and sending the query to filter the dropdown list (defaults to 500 ms)

Basic steps:

  1. Store creation and loading with a HttpProxy and a JsonReader

  2. Combobox creaction with REMOTE mode

  3. Linking the combobox with the store

  4. Creating the server side part (servlet or CGI) by analyzing the query parameter on the POST request.



Sample code:



         HttpProxy dataProxy = new HttpProxy("/rpc/adherents");
final String resultTpl = "

{nom} {prenom}

{email}
";
RecordDef adherentsRecordDef =
new RecordDef(new FieldDef[] { new StringFieldDef("id"),
new StringFieldDef("nom"),
new StringFieldDef("prenom"),
new StringFieldDef("telephoneFixe"),
new StringFieldDef("telephoneMobile"),
new StringFieldDef("email"),
new StringFieldDef("adresse"),
new StringFieldDef("codePostal"),
new StringFieldDef("ville") });
JsonReader reader = new JsonReader(adherentsRecordDef);
reader.setRoot("adherents");
Store store = new Store(dataProxy, reader);
store.load();
ComboBox cb = new ComboBox();
cb.setStore(store);
cb.setDisplayField("nom");
cb.setId("nom");
cb.setTypeAhead(false);
cb.setLoadingText("Searching...");
cb.setFieldLabel("Nom");
cb.setWidth(230);
cb.setTpl(resultTpl);
cb.setPageSize(10);
cb.setHideTrigger(true);
cb.setMode(ComboBox.REMOTE);
cb.setTitle("Users");
//cb.setHideLabel(true);
cb.setItemSelector("div.search-item");

Friday, November 16, 2007

GWT and Gears, automatic manifest generation

When you create an application with GWT and Gear (http://code.google.com/p/gwt-google-apis/), a manifest has to be created to manage offline content.
Depending the targeted browser (Safari, IE, Gecko1.8, Gecko, Opera) GWT generates specifics files for html and javascript.
To minimize the manifest management (and the downloading process) a dedicated manifest has to be created for each browser.
A solution is to use a ServletFilter. This filter will automaticaly return the manifest regarding user's browser.
The filter configuration manages manifest version and resources to be included in the manifest (.css, .html, .gif...).
This filter will only be trigged when the manifest file is requested by Gears.


I hope this solution can help..
Don' t hesitate to post any comments or modifications.

FilterServlet code:



package com.alu.gwt.server;
 
import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileReader;
import java.io.FilenameFilter;
import java.io.IOException;
 
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
 
public class GearManifestFilter implements Filter {
    public static final String EXTENSIONS = "extensions";
    public static final String MANIFEST_VERSION = "manifestVersion";
    
    public static final String OPERA_PATTERN = "'opera'],";
    public static final String SAFARI_PATTERN = "'safari'],";
    public static final String GECKO1_8_PATTERN = "'gecko1_8'],";
    public static final String GECKO_PATTERN = "'gecko'],";
    public static final String IE6_PATTERN = "'ie6'],";
 
    public static final String OPERA = "opera";
    public static final String SAFARI = "safari";
    public static final String GECKO1_8 = "gecko1_8";
    public static final String GECKO = "gecko";
    public static final String IE6 = "ie6";
 
    protected String _operaCacheFilename;
    protected String _safariCacheFilename;
    protected String _gecko1_8CacheFilename;
    protected String _geckoCacheFilename;
    protected String _ie6CacheFilename;
 
    protected String _extensions;
 
    protected String _manifestVersion = "1.0";
 
    protected Map _manifestMap = new HashMap();
 
    public static class GearRegexpFilenameFilter implements FilenameFilter {
        private String _regexpFilter;
        private Pattern _regexpPattern;
 
        GearRegexpFilenameFilter(String regexpFilter) {
            _regexpFilter = regexpFilter;
            _regexpPattern = Pattern.compile(_regexpFilter);
        }
 
        public boolean accept(File dir, String name) {
            if ((name.indexOf(".cache.") == -1) && 
                (name.indexOf("-xs.nocache.") == -1)) {
                Matcher matcher = _regexpPattern.matcher(name);
                return matcher.find();
            } else
                return false;
        }
    }
 
    public static class GearResourcesExplorer {
        List _result = new ArrayList();
 
        public GearResourcesExplorer() {
 
        }
 
        public List list(File directory, FilenameFilter filter, 
                         boolean recursive, boolean root) {
            if (recursive) {
                File[] dirs = directory.listFiles(new FileFilter() {
 
                            public boolean accept(File pathname) {
                                if (pathname.isDirectory())
                                    return true;
                                return false;
                            }
                        });
                for (int i=0; i < dirs.length; i++)
                    list(dirs[i], filter, recursive, false);
            }
            
            String[] res = directory.list(filter);
            for (int i = 0; i < res.length; i++) {
                if (root) {
                _result.add(res[i]);
                } else {
                    _result.add(directory.getName() + File.separator + res[i]);
                }
            }
            return _result;
        }
    }
 
    public GearManifestFilter() {
    }
 
    public void init(FilterConfig filterConfig) {
        _extensions = filterConfig.getInitParameter(EXTENSIONS);
        _manifestVersion = filterConfig.getInitParameter(MANIFEST_VERSION);
        System.out.println("Extensions: " + _extensions);
        String realPath = filterConfig.getServletContext().getRealPath("/");
        System.out.println("Path:" + realPath);
        File dir = new File(realPath);
        
        List resourceFilenames = new GearResourcesExplorer().list(dir, new GearRegexpFilenameFilter(_extensions), true, true);
        
        for (int i = 0; i < resourceFilenames.size(); i++) {
            System.out.println("File: " + resourceFilenames.get(i));
        }
 
        FilenameFilter noCacheFilter = new FilenameFilter() {
                public boolean accept(File dir, String name) {
                    if (name.endsWith(".nocache.js") && 
                        !name.endsWith("-xs.nocache.js"))
                        return true;
                    return false;
                }
            };
        String[] noCacheFilenames = dir.list(noCacheFilter);
        if (noCacheFilenames.length != 1) {
            System.out.println("Error !!");
        } else {
            getResourcesAccordingUserAgent(realPath + noCacheFilenames[0]);
        }
 
        _manifestMap.put(SAFARI, 
                         createManifest(_safariCacheFilename, _manifestVersion, 
                                        resourceFilenames));
        _manifestMap.put(GECKO, 
                         createManifest(_geckoCacheFilename, _manifestVersion, 
                                        resourceFilenames));
        _manifestMap.put(GECKO1_8, 
                         createManifest(_gecko1_8CacheFilename, _manifestVersion, 
                                        resourceFilenames));
        _manifestMap.put(OPERA, 
                         createManifest(_operaCacheFilename, _manifestVersion, 
                                        resourceFilenames));
        _manifestMap.put(IE6, 
                         createManifest(_ie6CacheFilename, _manifestVersion, 
                                        resourceFilenames));
    }
 
    protected void getResourcesAccordingUserAgent(String noCacheFilename) {
        System.out.println(noCacheFilename);
        BufferedReader in = null;
        StringBuffer buffer = new StringBuffer();
        try {
            in = new BufferedReader(new FileReader(noCacheFilename));
            String currentLine;
            while ((currentLine = in.readLine()) != null) {
                buffer.append(currentLine);
            }
            in.close();
        } catch (IOException e) {
            System.out.println("Error");
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
 
        _operaCacheFilename = getFilename(buffer, OPERA_PATTERN);
        _geckoCacheFilename = getFilename(buffer, GECKO_PATTERN);
        _gecko1_8CacheFilename = getFilename(buffer, GECKO1_8_PATTERN);
        _safariCacheFilename = getFilename(buffer, SAFARI_PATTERN);
        _ie6CacheFilename = getFilename(buffer, IE6_PATTERN);
 
        System.out.println("OperaFilename: " + _operaCacheFilename);
        System.out.println("GeckoFilename: " + _geckoCacheFilename);
        System.out.println("Gecko1_8Filename: " + _gecko1_8CacheFilename);
        System.out.println("SafariFilename: " + _safariCacheFilename);
        System.out.println("IE6Filename: " + _ie6CacheFilename);
 
    }
 
    protected String getFilename(StringBuffer noCacheStringBuffer, 
                                 String agentPattern) {
        String noCacheString = noCacheStringBuffer.toString();
        int beginIndex = 
            noCacheString.indexOf(agentPattern) + agentPattern.length() + 1;
        int endIndex = noCacheString.indexOf("'", beginIndex);
        String filename = noCacheString.substring(beginIndex, endIndex);
        return filename;
    }
 
    protected String createManifest(String userAgentHash, String version, 
                                    List resourceFilenames) {
        StringBuffer manifestBuffer = new StringBuffer("{\n");
        manifestBuffer.append("\"betaManifestVersion\": 1,\n");
        manifestBuffer.append("\"version\": \"");
        manifestBuffer.append(version);
        manifestBuffer.append("\",\n");
        manifestBuffer.append("\"entries\": [\n");
 
        manifestBuffer.append("{ \"url\": \"");
        manifestBuffer.append(userAgentHash + ".cache.js\"");
        manifestBuffer.append("},\n");
        manifestBuffer.append("{ \"url\": \"");
        manifestBuffer.append(userAgentHash + ".cache.html\"");
        manifestBuffer.append("},\n");
 
        for (int i = 0; i < resourceFilenames.size() - 1; i++) {
            manifestBuffer.append("{ \"url\": \"");
            manifestBuffer.append(resourceFilenames.get(i));
            manifestBuffer.append("\"},\n");
        }
        manifestBuffer.append("{ \"url\": \"");
        manifestBuffer.append(resourceFilenames.get(resourceFilenames.size() - 1));
        manifestBuffer.append("\"}\n");
        manifestBuffer.append("]\n");
        manifestBuffer.append("}\n");
        System.out.println("Manifest: " + manifestBuffer.toString());
        return manifestBuffer.toString();
    }
 
    public void doFilter(ServletRequest servletRequest, 
                         ServletResponse servletResponse, 
                         FilterChain filterChain) throws IOException, 
                                                         ServletException {
        HttpServletRequest request = ((HttpServletRequest)servletRequest);
        String userAgentString = request.getHeader("user-agent");
        System.out.println("UserAgent: " + userAgentString);
        String userAgent = getUserAgent(userAgentString);
        ServletOutputStream out = servletResponse.getOutputStream();
        HttpServletResponse response = (HttpServletResponse)servletResponse;
        response.setContentType("application/jsonrequest");
        out.println((String)_manifestMap.get(userAgent));
    }
 
    protected String getUserAgent(String userAgentString) {
        if (userAgentString.indexOf("IE6") != -1 || 
            userAgentString.indexOf("MSIE 7") != -1) {
            System.out.println("IE6 or more detected");
            return IE6;
        } else if (userAgentString.indexOf("MOZILLA") != -1) {
            System.out.println("MOZILLA detected");
            return GECKO;
        } else if (userAgentString.indexOf("MOZILLA") != -1) {
            System.out.println("MOZILLA detected");
            return IE6;
        }
        return SAFARI;
    }
 
    public void destroy() {
    }
 
}

web.xml:




<filter>
<filter-name>gearManifestFilter</filter-name>
<filter-class>com.alu.gwt.server.GearManifestFilter</filter-class>
<init-param>
<description>Resources to be include in manifest</description>
<param-name>extensions</param-name>
<param-value>.htm|.css|.html|.js|.gif|.jpg</param-value>
</init-param>
<init-param>
<description>manifest version</description>
<param-name>manifestVersion</param-name>
<param-value>1.0</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>gearManifestFilter</filter-name>
<url-pattern>/manifest.json</url-pattern>
</filter-mapping>

Frederic shared items