29 settembre 2011

GWT tutorial: GWT with PHP

Typically, web applications developed in GWT imply the use of a Java application server (eg. Tomcat, Jetty, Glashfish, etc). 
You can use a normal HTTP server like Apache or IIS to develop web services with server-side language of your choice.

Let us first of all how to create an ajax request into GWT client:

String url = "http://localhost/myservice.php";
RequestBuilder builder = new RequestBuilder(RequestBuilder.POST, url);
String postString = "par1=val1&par2=val2&par3=val3";
builder.setHeader("Content-Type", "application/x-www-form-urlencoded");
try {
    builder.sendRequest(postString, new RequestCallback(){
        @Override
        public void onResponseReceived(Request request, Response response) {
           if( 200==response.getStatusCode() ) {
               
               String jsonString = response.getText();
               MyObject myObj = parseJson(jsonString);
           }   
        }

        @Override
         public void onError(Request request, Throwable exception) {}
    });
}catch(RequestException e) {}

The "jsonString" string must then be deserialized into an object. 
If we use the json format is sufficient to create a JSNI method   to execute the "eval" javascript statement; if the format is in XML you can use a XMLparser.

native final MyObject parseJson(String json) /*-{
   return eval("("+ json +")");
}-*/;
where MyObject is a class which must extend "JavaScriptObject" and with methods needed to read the properties. 
class MyObject extends JavaScriptObject {
    protected MyObject(){}
    public final native String getMyProperty1()/*-{ return this.myProperty1 }-*/;
    public final native int getMyProperty2()/*-{ return this.myProperty2 }-*/;
}
json string returned from server should be something like this:
{ 'myProperty1':'hello', 'myProperty2':2534 }

You can create in php like this:
<?php
$response = array('myProperty1'=>'hello', 'myProperty2'=>2534);
echo json_encode($response);
?>

It's good practise to handle js error:
try {
    MyObject myObj = parseJson(jsonString);
}catch(JavaScriptException ex) {
     // handle parsing error
}

Said this, It remains, therefore, the problem of running service in "debug mode" on Eclipse (this modality uses a java application server listening on port 8888). 
The problem is due to "cross domain" browser restrictions . 
On the client side, you are at "127.0.0.1:8888" (ip address debug mode) and you need to call a service at "127.0.0.1:80" (apache http server). The browser will not permit this. 
To solve this problem, I created a GWT service (RemoteServiceServlet) called  DebugService . 
This service acts like a proxy and then simly submit your request by sending cookies (eg. php session id) and all HTTP POST parameters  .

Obviously you have to implement a code that checks when to use the debugging service, or contact the service php. For example, in this way: 
if(  GWT.getHostPageBaseURL().indexOf("http://127.0.0.1:8888") >= 0 ){
    //call debug service (RemoteService)
    DebugServiceAsync debugService = GWT.create(DebugService.class);
    debugService.callJsonService("par1=val1&par2=val2","http://localhost/myService.php", new AsyncCallback<String>(){
         public void onSuccess(String jsonString){
             //TODO handle json string
         }
         public void onError(Throwable caught) {
             // TODO handle error
         }
    });
}else {
   //call php service (RequestBuilder)
   RequestBuilder builder = new RequestBuilder( RequestBuilder.POST"http://localhost/myService.php");
    // ....
}


DebugServiceImpl.java (pkg server)

package it.gheryd.testgwtphp.server;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLConnection;

import javax.servlet.http.Cookie;

import it.gheryd.testgwtphp.client.jsonServices.DebugService;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;

public class DebugServiceImpl extends RemoteServiceServlet implements DebugService {

 @Override
 public String callJsonService(String postParams, String url) {
   HttpURLConnection  conn = null;
   InputStream is = null;
   try {
     URL phpService = new URL( url );
     conn = (HttpURLConnection )phpService.openConnection();
     conn.addRequestProperty("Content-Type", "application/x-www-form-urlencoded");
     boolean useCookies = false;
     if(this.getThreadLocalRequest().getHeader("Cookie")!=null) {
       conn.addRequestProperty("Cookie", this.getThreadLocalRequest().getHeader("Cookie"));
       useCookies = true;
     }
     if(postParams!=null && postParams.length()>0 ) {
       conn.setDoOutput(true);
       OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
       out.write(postParams);
       out.close();
     }
     if (conn.getResponseCode() >= 400) {
       is = conn.getInputStream();
     } else {
       is = conn.getErrorStream();
     }
     is = conn.getInputStream();
     BufferedReader in = new BufferedReader( new InputStreamReader(is) );
     String jsonResponse = "";
     String inputLine;
     while ((inputLine = in.readLine()) != null) jsonResponse += inputLine;
     in.close();
     if(useCookies){
        String headerName=null;
        for (int i=1; (headerName = conn.getHeaderFieldKey(i))!=null; i++) {
          if (headerName.equals("Set-Cookie")) {
              String cookie = conn.getHeaderField(i);
              cookie = cookie.substring(0, cookie.indexOf(";"));
              String cookieName = cookie.substring(0, cookie.indexOf("="));
              String cookieValue = cookie.substring(cookie.indexOf("=") + 1, cookie.length());
              this.getThreadLocalResponse().addCookie(new Cookie(cookieName, cookieValue));
          }
        }
     }
         
     return jsonResponse;
   } catch(Exception ex) {
      this.log("errore in debug\n"+"url: "+url+"\nconn:"+conn+" \nis:"+is, ex);
      return "{type:\"error\",value:{message:\""+ex.getClass().getSimpleName()+" - "+ex.getMessage()+"\"}}";
   }
 }

}
DebugService.java (pkg client)

package it.gheryd.testgwtphp.client.jsonServices;

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


@RemoteServiceRelativePath("debugService")
public interface DebugService extends RemoteService {

 public String callJsonService( String postParams, String url );
 
}
DebugServiceAsync .java

package it.gheryd.testgwtphp.client.jsonServices;

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

public interface DebugServiceAsync {

 void callJsonService(String postParams, String url, AsyncCallback callback);

}

The service JAVA must be declared in the file / WEB-INF/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">

  <!-- Servlets -->
  
  <!-- Default page to serve -->
  <welcome-file-list>
    <welcome-file>TestGwtPhp.html</welcome-file>
  </welcome-file-list>
  
  <servlet>
    <servlet-name>debugService</servlet-name>
    <servlet-class>it.gheryd.testgwtphp.server.DebugServiceImpl</servlet-class>
  </servlet>
  
  <servlet-mapping>
    <servlet-name>debugService</servlet-name>
    <url-pattern>/testgwtphp/debugService</url-pattern>
  </servlet-mapping>
</web-app>

At this point you know what you need to make an application with which you can make debugging of Eclipse (or NetBeans). 

If you want to see a practical example, after you find a project that I hope will be clarified. 
In this project I created a series of classes under the package "jsonService" that automate the ajax request process. 

The Eclipse project has this structure: 

As you can see, the files ". php" are in the "war" folder of the project. 

In my example I added in the configuration file httpd.conf Apache alias: 
httpd.conf 

Alias /testgwtphp "C:/Users/gheryd/workspace/TestGwtPhp/war/" 
<Directory "C:/Users/gheryd/workspace/TestGwtPhp/war/">
    Options Indexes FollowSymLinks MultiViews
    AllowOverride all
        Order Deny,Allow
 Deny from all
 Allow from 127.0.0.1
</Directory>

for which the services in PHP HTTP Server can be accessed at "http://localhost/testgwtphp/".

In the package it.gheryd.testgwtphp.client.jsonServices find the classes that use it for calls to PHP services. 
The class JsonService is used to create a ajax request 
This class automatically decides whether to make the request directly to the service or whether to call the PHP DebugService (RemoteServiceServlet). It also takes care to deserialize the JSON string bean object.  
The code below is highlighted in yellow the url used in debug  mode.

JsonService.java

package it.gheryd.testgwtphp.client.jsonServices;

import java.util.Map;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptException;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.user.client.rpc.AsyncCallback;

public class JsonService {

 private static final String TYPE_ERROR = "error";
 private static final String BASE_SERVICE_URL =  GWT.getHostPageBaseURL();
private static final String BASE_DEBUG_SERVICEURL = "http://127.0.1.1/testgwtphp/";
 
 public static boolean DEBUG = false;
 private DebugServiceAsync debugService =null;
 
 private String serviceUrl = null;
 
 
 public JsonService(String serviceUrl) {
  this.serviceUrl = serviceUrl;
 }
 
 public <T extends JavaScriptObject> void request(String jsonParams, JsonServiceCallback<T> callback) {
  try {
   String postString = createPostString(jsonParams);
   if(DEBUG) 
    startDebugRequest(postString, callback );
   else
    startRequest(postString, callback );
  }catch(JavaScriptException ex) {
   callback.onError("javascript exception: "+ex.getMessage()+" jsonParams: "+jsonParams);
  }
 }
 
 
 public <T extends JavaScriptObject> void request(Map<String,String> paramsMap, JsonServiceCallback<T> callback) {
  String postString = createPostString(paramsMap);
  if(DEBUG)
   startDebugRequest(postString, callback );
  else 
   startRequest(postString, callback);
 }
 
 
 private <T extends JavaScriptObject> void startDebugRequest(String postString, final JsonServiceCallback<T> callback) {
  if(debugService==null) {
   debugService = GWT.create(DebugService.class);
  }
  String url = BASE_DEBUG_SERVICEURL+serviceUrl;
  debugService.callJsonService(postString, url, new AsyncCallback<String>() {
   
   @Override
   public void onSuccess(String result) {
    handleSuccess(result, callback);
   }
   
   @Override
   public void onFailure(Throwable caught) {
    callback.onError(caught.getMessage());
   }
  });
 }
 
 
 private <T extends JavaScriptObject> void startRequest(String postString,  final JsonServiceCallback<T> callback) {
  String url = BASE_SERVICE_URL+serviceUrl;
  
  RequestBuilder builder = new RequestBuilder(RequestBuilder.POST, url);
  builder.setHeader("Content-Type", "application/x-www-form-urlencoded");
  
  try{
   builder.sendRequest(postString, new RequestCallback(){
    @Override
    public void onResponseReceived(Request request, Response response) {
     if( 200==response.getStatusCode() ) {
      handleSuccess(response.getText(), callback);
     }else {
      callback.onError( "error status:"+response.getStatusCode() );
     }    
    }

    @Override
    public void onError(Request request, Throwable exception) {
     callback.onError(exception.getMessage()); 
    }
    
   });
  }catch(RequestException e){
   callback.onError(e.getMessage());
  }
 }
 
 private <T extends JavaScriptObject> void handleSuccess(String json, JsonServiceCallback<T> callback) {
  ResponseBean responseBean = null;
  try {
   responseBean = deserializeResponse( json );
  }catch(JavaScriptException exception) {
   callback.onError("javascript exception: "+exception.getMessage()+" json->"+json);
   return;
  }
  
  if( TYPE_ERROR.equals(responseBean.getType()) ) {
   try {
    ErrorBean errorBean = (ErrorBean)responseBean.getValue();
    callback.onError( errorBean.getMessage() );
   }catch(JavaScriptException exception){
    callback.onError("error object deserialization:"+exception.getMessage()+" json: "+json );
   }
   
   return;
  }
  callback.onSuccess( (T)responseBean.getValue() );
 }
 
 private final native ResponseBean deserializeResponse(String json) /*-{
  var response = eval( "("+json+")" );
  if( !response.type ) throw "type is undefined";
  if( response.value===undefined ) throw "value is undefined";
  if( typeof(response.value) == "string" ) {
   response.value = {"str":response.value};
  }
  return response;
 }-*/;
 
 private final native String createPostString(String json)/*-{
  if(!json) return null;
  var obj = eval( "("+json+")" );
  var s = "";
  for(var k in obj) {
   s += (s.length==0?"":"&") + k + "=" + obj[k];
  }
  return s;
 }-*/;

 private String createPostString(Map<String,String> paramsMap) {
  if(paramsMap==null || paramsMap.size()==0 ) return null;
  String s = "";
  for(String k: paramsMap.keySet()) {
   s += (s.length()==0?"":"&") + k + "=" + paramsMap.get(k);
  }
  return s;
 }
}

The interface JsonServiceCallback is similar to that of GWT AsyncCallback ; in a similar manner should implement this interface to manage the response in case of success or failure.

JsonServiceCallback.java

package it.gheryd.testgwtphp.client.jsonServices;

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

public interface JsonServiceCallback <T extends JavaScriptObject> {
 
 public void onSuccess(T result);
 public void onError(String error);
 
}

The classes ResponseBean and ErrorBean are used internally to JsonService , and define the formats of the data that the service must return PHP (explained in more detail below)

ResponseBean.java

package it.gheryd.testgwtphp.client.jsonServices;

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

class ResponseBean  extends JavaScriptObject { 
 protected ResponseBean(){}
 
 public final native String getType() /*-{
  return this.type;
 }-*/;
 
 public final native JavaScriptObject getValue() /*-{
  return this.value;
 }-*/;
}

ErrorBean.java

package it.gheryd.testgwtphp.client.jsonServices;

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

public class ErrorBean extends JavaScriptObject {

 protected ErrorBean() {}
  
 public final native String getMessage() /*-{
  return this.message;
 }-*/;  
}

The class JsString is used if the data value returned by the service PHP is a simple string.
JsString.java

package it.gheryd.testgwtphp.client.jsonServices;

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

public class JsString extends JavaScriptObject{

 protected JsString() {}
  
 public final native String getString()/*-{
  return this.str;
 }-*/;
}

For completeness also follows the content of the GWT module configuration file: 
TestGwtPhp.gwt.xml

<?xml version="1.0" encoding="UTF-8"?>
<module rename-to='testgwtphp'>
<!-- module rename-to='testgwtphp' -->
  <!-- Inherit the core Web Toolkit stuff.                        -->
  <inherits name='com.google.gwt.user.User'/>

  <!-- Inherit the default GWT style sheet.  You can change       -->
  <!-- the theme of your GWT application by uncommenting          -->
  <!-- any one of the following lines.                            -->
  <inherits name='com.google.gwt.user.theme.clean.Clean'/>
  <!-- <inherits name='com.google.gwt.user.theme.standard.Standard'/> -->
  <!-- <inherits name='com.google.gwt.user.theme.chrome.Chrome'/> -->
  <!-- <inherits name='com.google.gwt.user.theme.dark.Dark'/>     -->

  <!-- Other module inherits                                      -->

  <!-- Specify the app entry point class.                         -->
  <entry-point class='it.gheryd.testgwtphp.client.TestGwtPhp'/>

  <!-- Specify the paths for translatable code                    -->
  <source path='client'/>
  <source path='shared'/>

</module>

Now that we have defined the basic classes for asynchronous communication client / server, including debug mode, we explain how to use this system. 
To make an ajax call is sufficient to instantiate the class and pass the url on JsonService of our service:

JsonService myService = new JsonService("MyService.php");

so the request will be made by calling the "request": 

myService.request(null, new JsonServiceCallback<MyBean>(){
    public void onSuccess(MyBean result) {
        //TODO response ok
    }
    public void onError(String error) {
       //TODO error
    }
});

The bean that will return me the service is to extend the class "com.google.gwt.core.client.JavaScriptObject" 

public class MyBean extends JavaScriptObject {
    protected MyBean() {}
    public final native String getMyProperty()/*-{
        return this.myPropery;
    }-*/;
}

PHP side of the service will return a JSON string in this format: 

{
   type: "MyBean",
   value: {myProperty: "Hello World!" }
}

or in case of error

{
   type: "error",
   value: {message: "This is a error"}
}

To pass the POST data in PHP Service must create a hash map: 

Map<String,String> postParams = new HashMap<String,String>();
postParams.put("myparam", "myvalue");
myService.request(postParams, new JsonServiceCallback<MyBean>(){
    public void onSuccess(MyBean result) {
        //TODO response ok
    }
    public void onError(String error) {
       //TODO error
    }
});

or alternatively you can pass a json string: 

String postParams = "{'myparam':'myvalue'}";

The service php must be something like this:

<?php
if( !isset($_POST["myparam"]) ) {
    echo json_encode( array("type"=>"error", "value"=>array("message"=>"missing post param") ));
    return;
}
echo json_encode( array("type"=>"MyBean", "value"=>array("myProperty"=>" gwt-php test ") ));
?>

As already explained, the service can return a simple string. In this case you must use the class JsString :

myService.request(postParams, new JsonServiceCallback<JsString>(){
    public void onSuccess(JsString result) {
        String s = result.getStr();
        //TODO response ok
    }
    public void onError(String error) {
       //TODO error
    }
});

The json response will then simply:

{
   type: "MyBean",
   value: "Hello World!"
}


Sample implementation 
In applying the test that I developed using the PHP session to store the names entered by the user via the GUI. 
The look and feel of this page: 
In the class JsonService was configured as the base url for use in debugging PHP services:

private static final String BASE_DEBUG_SERVICEURL = "http://127.0.1.1/testgwtphp/";

To enable debug mode should lead to true the static variable class JsonService , you can set it to automatically: 

JsonService.DEBUG = GWT.getHostPageBaseURL().indexOf("http://127.0.0.1:8888") >= 0;

At this point we see the GWT module of the test. 
TestGwtPhp.java

package it.gheryd.testgwtphp.client;

import java.util.HashMap;
import java.util.Map;

import it.gheryd.testgwtphp.client.jsonServices.JsString;
import it.gheryd.testgwtphp.client.jsonServices.JsonService;
import it.gheryd.testgwtphp.client.jsonServices.JsonServiceCallback;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;


/**
 * Entry point classes define <code>onModuleLoad()</code>.
 */
public class TestGwtPhp implements EntryPoint {

 private JsonService addUserService = new JsonService("AddUserService.php");
 private JsonService getUsersService = new JsonService("GetUsersService.php");
 private JsonService deleteUsersService = new JsonService("DeleteUsers.php");
 
 public void onModuleLoad() {
  
  final SimplePanel resultPanel = new SimplePanel();
  resultPanel.getElement().getStyle().setBackgroundColor("#EEE");
  resultPanel.getElement().getStyle().setProperty("border", "1px solid black");
  resultPanel.getElement().getStyle().setProperty("padding", "10px");
  
  VerticalPanel vp = new VerticalPanel();
  vp.setSpacing(10);
  
  final TextBox nameTB = new TextBox();
  final TextBox surnameTB = new TextBox();
  final CheckBox debugCB = new CheckBox("debug");
  
  HorizontalPanel hp = new HorizontalPanel();
  hp.add( new HTML("name: ") );
  hp.add( nameTB );
  hp.add( new HTML("surname: ") );
  hp.add( surnameTB );
  vp.add(hp);
  
  Button addUserBtn = new Button("add user");
  Button getUsersBtn = new Button("get users");
  Button deleteUsersBtn = new Button("delete users");
  
  hp = new HorizontalPanel();
  hp.add(addUserBtn);
  hp.add(getUsersBtn);
  hp.add(deleteUsersBtn);
  hp.add(debugCB);
  
  vp.add(hp);
  vp.add(resultPanel);
  final VerticalPanel usersPanel = new VerticalPanel();
  vp.add(usersPanel);
  
  debugCB.addClickHandler(new ClickHandler() {
   @Override
   public void onClick(ClickEvent event) {
    JsonService.DEBUG = debugCB.getValue();
    resultPanel.setWidget( new HTML("debug mode: "+JsonService.DEBUG) );
   }
  });
  
  debugCB.setValue( GWT.getHostPageBaseURL().indexOf("http://127.0.0.1:8888") >= 0 , true );
  
  JsonService.DEBUG = debugCB.getValue();
  
  addUserBtn.addClickHandler(new ClickHandler() {
   @Override
   public void onClick(ClickEvent event) {
    
    Map<String,String> params = new HashMap<String,String>();
    params.put("name", nameTB.getText());
    params.put("surname", surnameTB.getText());
    addUserService.request(params, new JsonServiceCallback<JsString>(){

     @Override
     public void onSuccess(JsString result) {
      resultPanel.setWidget( new HTML("Result: "+result.getString()) );
     }

     @Override
     public void onError(String error) {
      resultPanel.setWidget( new HTML("<span style=\"color:red;\">ERROR!</span> "+error) );
     }
     
    });
   }
  });
  
  getUsersBtn.addClickHandler(new ClickHandler() {
   @Override
   public void onClick(ClickEvent event) {
    usersPanel.clear();
    String params = null;
    getUsersService.request(params,  new JsonServiceCallback<JsArray<UserBean>>(){

     @Override
     public void onSuccess(JsArray<UserBean> result) {
      resultPanel.setWidget( new HTML("users: "+result.length()) );
      for(int i=0; i<result.length(); i++) {
       UserBean ub = result.get(i);
       usersPanel.add( new HTML( ub.getName()+" "+ub.getSurname() ) );
      }
     }

     @Override
     public void onError(String error) {
      resultPanel.setWidget( new HTML("<span style=\"color:red;\">ERROR!</span> "+error) );
     }
     
    });
    
   }
  });
  
  deleteUsersBtn.addClickHandler(new ClickHandler(){
   @Override
   public void onClick(ClickEvent event) {
    String params = null;
    deleteUsersService.request(params, new JsonServiceCallback<JsString>() {
     @Override
     public void onSuccess(JsString result) {
      resultPanel.setWidget( new HTML("Result: "+result.getString()) );
     }

     @Override
     public void onError(String error) {
      resultPanel.setWidget( new HTML("<span style=\"color:red;\">ERROR!</span> "+error) );
     }
     
    });
    
   }
  });
  
  RootPanel.get().add(vp);
  
 }
 
}

It 's interesting use of the class JsArray (see gwt api) used to switch the list of objects UserBean . Not necessarily have to use a bean object that you define, you can pass any object that extends JavaScriptObject . 

The bean that I used is as follows: 
UserBean.java

package it.gheryd.testgwtphp.client;

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

public class UserBean extends JavaScriptObject {
 protected UserBean() {}

 public final native String getName()/*-{
  return this.name;
 }-*/;
  
 public final native String getSurname()/*-{
  return this.surname;
 }-*/; 
}

And finally the PHP services used: 
AddUserService.php

<?php 

if( !isset($_POST) ) {
 echo json_encode( array("type"=>"error", "value"=>array("message"=>"missing post params"))  );
 return;
}else if( empty($_POST["name"]) ) {
 echo json_encode( array("type"=>"error", "value"=>array("message"=>"missing post param 'name'"))  );
 return;
}else if( empty($_POST["surname"])  ) {
 echo json_encode( array("type"=>"error", "value"=>array("message"=>"missing post param 'surname'"))  );
 return;
}

$name = $_POST["name"];
$surname = $_POST["surname"];

session_start();

$response = array();

if( !isset($_SESSION["list"]) ) $_SESSION["list"] = array();
 
$_SESSION["list"][] = array("name"=>$name, "surname"=>$surname);

$response["type"] = "result";
$response["value"] = "ok";

echo json_encode($response);

?>

DeleteUsers.php

<?php 

session_start();

unset($_SESSION["list"]);

echo json_encode( array("type"=>"result", "value"=>"ok") );

?>

GetUsersService.php

<?php 

session_start();

if( !isset($_SESSION["list"]) )  {
 echo json_encode( array("type"=>"error", "value"=>array("message"=>"no list in session"))  );
 return;
};

$response = array("type"=>"userList" );
$response["value"] = $_SESSION["list"];

echo json_encode($response);
?>

We can now run our application from Eclipse: 
while to run the application directly from Apache, you must first compile it: 
Once completed you can access the url "127.0.0.1/testgwtphp/TestGwtPhp.html". 
Remember to start Apache!

Deepening 
I have not focused on the operation of the class JsonService but if the look is not very complex. It's definitely improved in several respects. 
Error Handling 
For example, error handling is dealt with very easily if not trivial, but in a real application would be more practical to treat the error as if it were an exception. In this case the interface ErrorBean could be enriched by other properties such as the name of an exception report to the client. 
For example, if the session expired the service may respond with an error like this: 

{
    type: "error",
    value: {message:"session expired", exception:"SessionExpiredException"} 
}

Then create a class exception on the client package:

public class SessionExpiredException extends Exception {
    public SessionExpiredException (String message) {  
       super(message);
    }
}

In this case the method onError interface JsonServiceCallback this will be the exception rather than the message string. 

package it.gheryd.testgwtphp.client.jsonServices;

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

public interface JsonServiceCallback  {
 
 public void onSuccess(T result);
 public void onError(Exception Ex);
 
}

To verify the type of exception would be enough to control implementation JsonServiceCallback:

myService.request(null, new JsonServiceCallback<MyBean>(){
    public void onSuccess(MyBean result) {
        //TODO response ok
    }
    public void onError(Exception ex) {
       if(ex instanceof SessionExpired) {
           //TODO session expired
       }else {
           //Window.alert(ex.getMessage());
       }
    }
});

In the class JsonService must be passed the instance of an exception: 

if( "SessionExpiredException".equals(errorBean.getException()) )
   callback.onError( new SessionExpiredException(errorBean.getMessage()) );

Obviously you must add the property "exception" in the class ErrorBean : 

package it.gheryd.testgwtphp.client.jsonServices;

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

public class ErrorBean extends JavaScriptObject {

 protected ErrorBean() {}
  
 public final native String getMessage() /*-{
  return this.message;
 }-*/; 
 
 public final native String getException() /*-{
  return this.exception;
 }-*/; 
}

The methods JSNI 
When you create a class that extends JavascriptObject, care must be taken to the return type in JSNI methods. In the following sample code is provided, for example, the type of return value: 

public  class MyObject extends JavaScriptObject{
    protected MyObject(){}
    public final native int getIntNumber()/*-{
        return isNaN(this.intNumber)?0:this.intNumber;
    }-*/;
    public final native String getSomeString()/*-{
        return typeof(this.someString)=="string"? this.someString : "";
    }-*/;
}
This avoids errors in the code difficult to detect. Within JSNI methods you can put a notification to identify the causes of the error: 

public  class MyObject extends JavaScriptObject{
    protected MyObject(){}
    public final native int getIntNumber()/*-{
        if(isNaN(this.intNumber)){
            console.log("***error***");
            console.log(this);
            this.intNumber = 0;
        }
        return this.intNumber;
    }-*/;
}

Hash Map
If a service needs to return a map of objects (an associative array in PHP), client-side GWT we can implement a class for the purpose: 

public class JHashMap <V extends JavaScriptObject> extends JavaScriptObject{
     protected JHashMap(){}
     public final native JsArrayString keys()/*-{
         return this.keys;
     }-*/;
     public final native V get(String key)/*-{
         return this.values[key];
     }-*/;
}
PHP side of the map should be created as: 
<?php
    $objectList = array(
        "obj1" => array("property1"=>"value1","property2"=>"value2"),
        "obj2" => array("property1"=>"value1","property2"=>"value2"),
        "obj3" => array("property1"=>"value1","property2"=>"value2")
    );
    $map = array(
         "keys" => array_keys($objectList),
         "values" => $objectList
    );
    echo json_encode( array("type"=>"JHashMap", "value"=>$map ) );
?>
From GWT client we manage well the answer:

myservice.request(null, new JsonServiceCallback<JHashMap<MyBean> >(){
     @Override
     public void onSuccess(JHashMap<MyBean> result) {
         JsArrayString keys = result.keys();
         for(int i=0; i<keys.length(); i++ ){
             MyBean myBean = result.get(keys.get(i));
             // ....
         }
     }

     @Override
     public void onError(String error) {
        //handle error
     }
});

17 settembre 2011

HTML: Utilizzo corretto degli attributi

Indice dei tutorials: http://gheryd.blogspot.com/2011/06/javascript-gwt-tutorials.html

Conoscere l'uso appropriato di questi attributi può semplificare il codice HTML, CSS e JAVASCRIPT e ritengo agevoli la manutenzione.

Attributo "id"
Quando il browser carica una pagina, verifica l'esistenza di questo attributo. Se l'attributo "id" viene specificato su un tag, il browser aggiunge il riferimento al relativo elemento in una mappa. Così in javascript è possibile accedere velocemente all'elemento con questa istruzione:
var el = document.getElementById("myId");
Questa istruzione restituisce un solo elemento HTML.
Se il valore dell'attributo "id" fosse duplicato, l'istruzione javascript non lavorerebbe adeguatamente.
Quindi il valore di un "id" non deve essere duplicato come in questo esempio:
<html>
<body>
    <div id="container">...</div>
    <div id="container">...</div>
</body>
</html>
Alcuni grafici css utilizzano l'attributo "id" per definire uno stile CSS:
#container {
     padding: 10px;
}
ma come spiegato precedentemente, lo scopo dell'attributo "id" è quello di accedere in maniera veloce ad un determinato elemento tramite javascript.
Per Definire le proprietà grafiche consiglio di far riferimento all'attributo "class".

Attributo "class"
Questo attributo viene utilizzato per assegnare ad un certo elemento HTML uno stile grafico definito tramite foglio di stile CSS.
Molti utilizzano l'attributo "id" perché danno per scontato che quell'elemento sarà unico. Tuttavia, dato che spesso e volentieri le strutture grafiche di una pagina HTML subiscono cambiamenti nel tempo, è sempre meglio utilizzare riferimenti all'attributo "class":
<html>
<body>
    <div class="mainContainer">...</div>
</body>
</html>
perché cambiando il valore della classe, e quindi l'aspetto grafico, non sarà necessario modificare per quanto possibile il codice javascript che probabilmente utilizzerà gli attributi "id" per accedere agli elementi html.
In realtà con alcune librerie javascript (es.jquery) è possible utilizzare uno dei valori di "class" per accedere ad uno o più elementi (vedere la sezione "Selettori jQuery").

Attributo "name"
In questa sezione parleremo dell'attributo "name" in riferimento agli elementi  (input,select) usati nel form (questo attributo può essere utilizzato anche sul tag "<iframe>", <a> o <img>, per esempio).
Quindi, nel contesto di un form, questo attributo viene utilizzato per specificare i parametri da inviare al server con il metodo "GET" o "POST".
A differenza dell'attributo "id", può aver senso duplicare il valore di questo attributo su piu tag in due casi:
1.Attributo name duplicato su diversi form:
<html>
<body>
    <form id="form1" method="POST" action="url1">
         indirizzo casa:<input name="address" type="text"/>
         <button type="submit" >invia</button>
    </form>
    <form id="form2" method="post" action="url2">
         indirizzo ufficio<input name="address" type="text"/>
         <button type="submit" >invia</button>
    </form>
</body>
</html>
Come potete constatare, esistono due campi "input" che hanno lo stesso valore dell'attributo "name", ma sono inseriti in due form diversi. Quindi il submit in un form non crea conflitto con l'altro input inserito nell'altro form.

2. Attributo duplicato su un elemento "submit":

<form id="form1" method="POST" action="url1">
         indirizzo casa:<input name="address" type="text"/>
         <input type="submit" name="azione" value="aggiungi"/>
        <input type="submit" name="azione" value="cancella"/>
</form>

In questo caso il valore che arriverà al server dipende dal bottone premuto. Se infatti viene premuto il tasto con name="aggiungi", al server verranno invitati in POST i valori "address" (che dipende da ciò che è inserito nella textbox) e azione="aggiungi". Se invece viene premuto il tasto "cancella", verrà inviato sempre "address" e azione ="cancella". Quindi a seconda del valore "azione", il codice lato server sa cosa deve fare con il valore di "address". Se in form prevede un solo tasto, potrebbe non esser necessario specificare l'attributo name per il tasto "submit".

Alcuni usano mettere il valore dell'attributo "id" uguale al valore dell'attributo "name":
<input id="address" name="address" type="text"/>
Questo non è necessario perché come è stato spiegato l'attributo "id" viene utilizzato lato client (codice javascript) mentre l'attributo "name" definisce il parametro da inviare al server.
Tuttavia è possibile via javascript utilizzare "name" per accedere direttamente ad un elemento:
var addressEl = document.getElementById("form1").address;
anche se consiglio sempre l'attributo "id". Uno dei motivi è spiegato in seguito.
Un errore comune che può capitare è assegnare all'attributo "name" un valore che in javascript potrebbe
scatenare un errore in quanto il suo valore corrisponde ad una parola riservata javascript.
Per esempio:
<input type="submit" name="submit" value="invia" />
se vogliamo avviare il "submit" di un form tramite javascript si genera un errore.
document.getElementById("form1").submit();
per il semplice motivo che la proprietà  "submit" non fa più riferimento ad una funzione ma ad un elemento del form. Dato che chi scrive  HTML non deve necessariamente conoscere javascript, per il programmatore javascript, onde evitare questi imprevisti è sconsigliabile accedere agli elementi tramite l'attributo name.


Selettore jQuery
jQuery fornisce una potente e semplice funzionalità per selezionare uno o più elementi sulla nostra pagina HTML tramite il nome di classi e nomi di attributi (nei browser di ultima generazione questo tipo di selezione si può effettuare anche in javascript nativo):
var list = jQuery("input[name=address] .lighted");
Tuttavia questo potente strumento deve essere usato in maniera adeguata se non vogliamo compromettere le prestazioni di esecuzione del codice.
Per esempio:
$(".myButtonDiv").click(function(event){
          alert("clicked");
});
Questo selettore può essere molto pesante su certi browser e su certe tipologie di computer (es.netbook) in quanto richiede una lunga scansione di tutti gli elementi della pagina HTML.
In questo caso, per migliorare le prestazioni, può aver senso aggiungere un attributo "id" al contenitore degli elementi che vogliamo selezionare:
$("#menuContainer .myButtonDiv").click(function(event){
          alert("clicked");
});
Questo è sicuramente più veloce perché identifichiamo prima di tutto un elemento contenitore tramite l'attributo "id" che come già spiegato avviene in maniera veloce, e successivamente vengono cercati tutti i suoi elementi figli con la classe "myButtonDiv". Quindi la ricerca è limitata solo agli elementi figli del contenitore e non a tutto il documento HTML.