Monday, 21 September 2009

Your first java appengine application

Hi folks,

This article is the first in a series on how to develop Google App Engine compliant applications. Anybody can jump in, whether a novice or more advanced. If you have any question, just post them up and I will try to answer them promptly.

Prerequisites :


JDK 1.6

App Engine SDK 1.2.2 (Google Eclipse plugin)
GWT 1.6.4
SmartGWT 1.2
GWT-ent (reflection enabler)

Forms-abstraction  (an abstraction on top of smartgwt written by me to simplify forms use)

So, let's start off with a simple CRUD application. Just to keep things simple, this tutorial will concentrate on developting the user interface (in SmartGWT)  and the server side operations (Spring MVC) for retrieving and persisting data. The two layers will communicate through JSON (Javascript Object Notation). JSON is a convenient way to serialize and deserialize your objects from the client to the server and vice versa.



Server side

What we need is a comfortable MVC implementation.Any ideas? Well my choice goes for Spring MVC. It's very simple to use. You just concentrate on your business logic and can add service level requirements later on (like security or transactions for example). I tried to use Struts 2 for a while, but no offense, it's not meant for a production environment and quite frankly has many bugs, lacks documentation and provides a weird, psycho-nightmarish kind of a scripting language called OGNL (there is no developer feedback for invalid properties or OGNL Expressions, not to mention other issues ...).


Run time environment

In order to make your web application run, be sure to add the following libraries to your WEB-INF/lib directory. The jars are already available in the demo application. Also be sure to add them to build path.





Build classpath

You would need some of these jars to make your code compile so be sure to add the following to your build path :

org.springframework.web.servlet-3.0.0.M2.jar
org.springframework.beans-3.0.0.M2.jar
org.springframework.context-3.0.0.M2.jar
org.springframework.core-3.0.0.M2.jar
org.springframework.expression-3.0.0.M2.jar
org.springframework.web-3.0.0.M2.jar
gson-1.3.jar







Define your model

I will be dealing with only one entity. I chose to persist an agenda contact entity, Nothing more than a POJO enhanced with persistence annotations.



Define the AgendaEntry class with three attributes id, firstName, and lastName. Be sure to add
the appropiate annotations for defining the primary key and persistence. What are these annotations for? Well, in runtime, when you will retrieve an agendaEntry object from the datastore through JDO, you will actually receive a proxy object implementing your model.

I also added the flgNull attribute to mark this object as logically deleted. I like to keep a trace of my data whatever happens.

To find out more on defining data classes please visit Google's tutorial  here


package javagoogleappspot.examples.model;

import java.io.Serializable;

import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class AgendaEntry implements Serializable {
 @PrimaryKey
 @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
 private Long id;
 
 @Persistent
 private String firstName;

 @Persistent
 private String lastName;
 
 @Persistent
 private String flgNull = "N";
 
 public String getFlgNull() {
  return flgNull;
 }

 public void setFlgNull(String flgNull) {
  this.flgNull = flgNull;
 }

 public Long getId() {
  return id;
 }

 public void setId(Long id) {
  this.id = id;
 }

 public String getFirstName() {
  return firstName;
 }

 public void setFirstName(String firstName) {
  this.firstName = firstName;
 }

 public String getLastName() {
  return lastName;
 }

 public void setLastName(String lastName) {
  this.lastName = lastName;
 }

}


Define your data access layer

Encapsulate your access calls behind a DAO interface. This layer would hide from the controlller the details of accessing Google's datastore. I provided  five simple methods for the CRUD operations. Considering that JDOQL provided by Google is missing any 'like' filtering criteria, I added such one on the find(String) method. Our like criteria must match the beginning characters, hence, it will not search for matches that occur within a string of characters.

The 'like' implementation is achieved by finding matches within two strings.That is any match must be bigger or equal to the given string and smaller than the next string obtained by commuting the last character to a higher one. For example to match any strings that match 'abc', one needs to find any matches between 'abc' and 'abcd'.

package javagoogleappspot.examples.dao;

import java.util.ArrayList;
import java.util.List;

import javagoogleappspot.examples.model.AgendaEntry;
import javagoogleappspot.examples.util.PMF;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;

import com.google.appengine.repackaged.com.google.common.base.StringUtil;


public class AgendaEntryDAOImpl implements AgendaEntryDAO {

 public void create(AgendaEntry agendaEntry) {
  PersistenceManager persistenceManager = PMF.get()
  .getPersistenceManager();
  persistenceManager.makePersistent(agendaEntry);
  persistenceManager.close();
 }

 public void delete(Long agendaEntryId) {
  PersistenceManager pm = PMF.get().getPersistenceManager();
  try {
   AgendaEntry e = pm.getObjectById(AgendaEntry.class, agendaEntryId);
   e.setFlgNull("Y");
  } finally {
   pm.close();
  }
 }


public List<AgendaEntry> find(String name) {
  StringBuilder filter = new StringBuilder("flgNull == \"N\"");
  StringBuilder parametersString = new StringBuilder();
  
  PersistenceManager pm = PMF.get().getPersistenceManager();
  List entries;
  Query query = pm.newQuery(AgendaEntry.class);
  List<Object> parameters = new ArrayList<Object>();
  boolean filterUsed = false;

  if (!StringUtil.isEmpty(name)){
   filter.append(" && lastName >= nameStringMin &&  lastName < nameStringMax "); 
   
   char charMax= name.charAt(name.length()-1);
   charMax = (char)((int)charMax + 1);      
   StringBuilder sbMax = new StringBuilder(name.substring(0,name.length()-1));
   sbMax.append(charMax);
   

   
   parametersString.append("String nameStringMin,String nameStringMax");
   parameters.add(name);   
   parameters.add(sbMax.toString());   
   
   filterUsed = true;
  }


  query.declareParameters(parametersString.toString());  
  query.setFilter(filter.toString());
  
  try {
   if (parameters.size()==0)
    query.setOrdering("id asc");       
   entries = (List<AgendaEntry<) query.executeWithArray(parameters.toArray());
   entries.size();
   return entries;
  } finally {
   pm.close();
  }

 }

public List findAll() {
  PersistenceManager pm = PMF.get().getPersistenceManager();
  List entries;
  Query query = pm.newQuery(AgendaEntry.class);
  query.setFilter("flgNull == \"N\"");
  query.setOrdering("id asc");
  try {

   entries = (List) query.execute();
   entries.size();
   return entries;
  } finally {
   pm.close();
  }  
 }

 public AgendaEntry getById(Long agendaEntryId) {
  PersistenceManager pm = PMF.get().getPersistenceManager();
  List users;
  Query query = pm.newQuery(AgendaEntry.class);
  query.setFilter("flgNull == \"N\" && id == selectedID");  
  query.declareParameters("Long selectedID");
  try {
   users = (List) query.execute(agendaEntryId);
   users.size();
   return users.get(0);
  } finally {
   pm.close();
  } 
 }

 public void update(AgendaEntry agendaEntry) {
  PersistenceManager pm = PMF.get().getPersistenceManager();
  try {
   AgendaEntry attachedE = pm.getObjectById(AgendaEntry.class, agendaEntry.getId());
   attachedE.setFirstName(agendaEntry.getFirstName());
   attachedE.setLastName(agendaEntry.getLastName());

  } finally { 
   pm.close();
  }
 }

}




 

Define your controller

Rather than defining a single controller object for each CRUD operation, I would opt for a coarse grained controller that would include all calls.


package javagoogleappspot.examples.controller;

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

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.mvc.multiaction.MultiActionController;

import javagoogleappspot.examples.util.JSONView;
import javagoogleappspot.examples.dao.AgendaEntryDAO;
import javagoogleappspot.examples.model.AgendaEntry;

public class ManageAgendaController extends MultiActionController {
    private AgendaEntryDAO agendaEntryDAO;

    public ModelAndView list(HttpServletRequest req,
            HttpServletResponse resp) throws Exception {
        
        String name = req.getParameter("name");
        
        List agendaEntries = agendaEntryDAO.find(name);            
        Map model = new HashMap();        
        ModelAndView modelAndView = new ModelAndView();
        model.put("jsonResponse", agendaEntries);   
        modelAndView.addAllObjects(model);
        View jsonView = new JSONView();
        modelAndView.setView(jsonView);
        return modelAndView;        
    }


    public ModelAndView getById(HttpServletRequest req,
            HttpServletResponse resp) throws Exception {
        String idString = req.getParameter("id");
        Long id = Long.parseLong(idString);
        AgendaEntry agendaEntry = agendaEntryDAO.getById(id);            
        Map model = new HashMap();        
        ModelAndView modelAndView = new ModelAndView();
        model.put("jsonResponse", agendaEntry);   
        modelAndView.addAllObjects(model);
        View jsonView = new JSONView();
        modelAndView.setView(jsonView);
        return modelAndView;        
    }


    
    
    public ModelAndView create(HttpServletRequest req,
            HttpServletResponse resp) throws Exception {
        
        AgendaEntry agendaEntry = new AgendaEntry();
        String firstName = req.getParameter("firstName");
        agendaEntry.setFirstName(firstName);
        String lastName = req.getParameter("lastName");
        agendaEntry.setLastName(lastName);

        agendaEntryDAO.create(agendaEntry);
        ModelAndView modelAndView = new ModelAndView();
        View jsonView = new JSONView();
        modelAndView.setView(jsonView);
        return modelAndView;        
    }

    
    public ModelAndView update(HttpServletRequest req,
            HttpServletResponse resp) throws Exception {
        String id = req.getParameter("id");        
        String firstName = req.getParameter("firstName");
        String lastName = req.getParameter("lastName");

        AgendaEntry agendaEntry = new AgendaEntry();
        agendaEntry.setId(Long.parseLong(id));
        agendaEntry.setFirstName(firstName);
        agendaEntry.setLastName(lastName);
        agendaEntryDAO.update(agendaEntry);
        ModelAndView modelAndView = new ModelAndView();
        View jsonView = new JSONView();
        modelAndView.setView(jsonView);
        return modelAndView;        
    }
    public ModelAndView delete(HttpServletRequest req,
            HttpServletResponse resp) throws Exception {
        String identityString =  req.getParameter("ids");
        String[] ids = identityString.split(",");
        for(String id:ids){
            Long identity = Long.parseLong(id);
            agendaEntryDAO.delete(identity);
        }
        
        ModelAndView modelAndView = new ModelAndView();
        View jsonView = new JSONView();
        modelAndView.setView(jsonView);
        return modelAndView;        
    }


    public void setAgendaEntryDAO(AgendaEntryDAO agendaEntryDAO) {
        this.agendaEntryDAO = agendaEntryDAO;
    }
    

}


Define your view

We need to output our model to the client in a JSON format. This conversion has to be seemless. We will define a JSON rendering view that will serialize our models:


package javagoogleappspot.examples.util;

import java.io.OutputStream;
import java.util.Date;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.View;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class JSONView implements View {

    public String getContentType() {
          return "application/x-javascript";
    }

    public void render(Map map, HttpServletRequest request,
            HttpServletResponse response) throws Exception {
        Gson gson = new GsonBuilder().registerTypeAdapter(Date.class, new DateTypeAdapter()).create();
        
        String jsonResponse = "[]";
        Object model = null;

        OutputStream outputStream = response.getOutputStream();
        response.setContentType("application/x-javascript");                      
        if (map!=null)
            model = map.get("jsonResponse");
        if (model==null)
            jsonResponse = "[]";
        else{
            jsonResponse = gson.toJson(model);                                    
        }
        
        response.getOutputStream().write(jsonResponse.getBytes());

        
    }

}





Spring configuration

We need to tell our servlet container that we have a spring servlet. We'll register our Spring dispatcher, and its mapping in the web.xml configuration file. We will also define Agenda.html as the default page for this web app.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>

    <servlet>
        <servlet-name>spring-web-json
        <servlet-class>org.springframework.web.servlet.DispatcherServlet
        <load-on-startup>1
</servlet>

    <servlet-mapping>
        <servlet-name>spring-web-json
        <url-pattern>*.json
    </servlet-mapping>
    <welcome-file-list>
  <welcome-file>Agenda.html
  </welcome-file-list>
</web-app>



Now that we have defined our beans, controller, dao, and view, let's put them all togther. We'll define our Spring servlet configuration file, namely, spring-web-json-servlet.xml:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
          "http://www.springframework.org/dtd/spring-beans-2.0.dtd">

<beans>
    <bean name="agendaEntryDAO" class="javagoogleappspot.examples.dao.AgendaEntryDAOImpl"/>
        
    <bean id="paramResolver" class="org.springframework.web.servlet.mvc.multiaction.ParameterMethodNameResolver">
        <property name="paramName">action
    </bean> 
    
    <bean name="manageAgendaController" class="javagoogleappspot.examples.controller.ManageAgendaController">
         <property name="agendaEntryDAO">
            >ref bean="agendaEntryDAO"/>
        </property>    
        <property name="methodNameResolver">
            <ref bean="paramResolver"/>
        </property>        
        
    </bean>
    <bean name="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/manageAgendaController.json">manageAgendaController
            </props>
        </property>
    </bean>
    
</beans>





You would probably ask me why didn't I just use annotations for injection instead of bothering to manually register the beans in the servlet configuration file and define the appropriate setters. Well, I did try it and it did not work. The annotations are being ignored at run time.

Testing your controller


Before running the integrated Jetty servlet container, make sure you have your GWT settings are disabled, otherwise Eclipse will ask you to provide a GWT module and we do not have it yet.







Now, launch your integrated Jetty in debug mode. Go to Run->Debug configurations (you will get to the following screen), select Agenda under Web application on your left pane and press debug:





Now when you're server is up. You should get this message on your console :

The server is running at http://localhost:8080/

Open a browser and calll the create and list methods on your controller :

Create an agendaEntry entry with firstName 'test' and lastName 'test':


http://localhost:8080/manageAgendaController.json?action=create&firstName=test&lastName=test


List your dataEntry entities :


http://localhost:8080/manageAgendaController.json?action=list


You should get the following json listing:


[{"id":1,"firstName":"test","lastName":"test","flgNull":"N"}]


Client side


Now do we have our controller set up, we need a nice looking and convincing client that will communicate with our MVC controller through JSON calls.

My choice went for SmartGwt. Why? Because it's cool and FREE! I do not want to be limited by any license constraints from the very start. There is Ext JS out there, but I didn't even bother to look at it. On top of that, the SmartGwt 1.2 version is stable.



GWT module

Define your GWT module in a package that will be lower in hierarchy than the server components created earlier. I will place it under javagoogleappspot.examples.web.client


Be sure you created the module configuration file Init.gwt.xml . It should be placed one level higher at javagoogleappspot.examples.web . Define inside your dependencies by using the tag  <inherits> and your entry point. The entry point is the first callback object that the javascript container will call.


Init.gwt.xml



 

 
<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 1.6.4//EN" 
"http://google-web-toolkit.googlecode.com/svn/tags/1.6.4/
distro-source/core/src/gwt-module.dtd"> 
<module>
  <!-- Inherit the core Web Toolkit stuff.                        -->
 <inherits name='com.google.gwt.user.User' />
 <inherits name="com.smartgwt.SmartGwt" />
 <inherits name="javax.validation.Validation" />
 <inherits name="com.google.gwt.validation.Validation" />
 <inherits name='com.google.gwt.junit.JUnit' />
 <inherits name="com.gwtent.GwtEnt" />
 <inherits name='com.sixqos.gwt.Forms' /> 
 <entry-point class='javagoogleappspot.examples.web.client.Init' /> 
 </module>




Classpath

To compile your client code, add the following jars to your classpath under Project -> Properties -> Java Build Path -> Libraries :

smartgwt.jar
gwt-forms.jar
gwtent.jar
junit.jar
gwt-validation-1.0.jar





Define your DTO

Well the JSON information that we receive on the client must be deserialized into an object. We'll define our AgendaEntryDTO holding attributed id,firstName, and lastName. Make sure to implement the Reflection interface so that this object be mapped seemlessly at runtime.

Define your search panel components


We will need :
  • a grid component for showing records, 
  • a filter to perform search 
  • detail window to update or insert new records.

Grid component

package javagoogleappspot.examples.web.client.forms;

import javagoogleappspot.examples.web.client.dto.AgendaEntryDTO;
import com.sixqos.gwt.client.filter.Filter;
import com.sixqos.gwt.client.filter.SearchForm;
import com.smartgwt.client.widgets.grid.ListGridField;

public class AgendaEntryFilter extends Filter {
    @Override
    public SearchForm createSearchForm() {
        SearchForm searchForm = new AgendaEntrySearchForm(this);
        return searchForm;
    }
    
    public  String getDetailFormName(){
        return AgendaEntryDetail.class.getName();
    }
    
    public ListGridField[] getGridColumns() {
        ListGridField id = new ListGridField("id");
        id.setHidden(true);
        ListGridField firstName = new ListGridField("firstName", 100);
        ListGridField lastName = new ListGridField("lastName", 100);
        
        return new ListGridField[] { id, firstName,lastName };

    }
    
    public String[] getDTONames(){
        return new String[]{AgendaEntryDTO.class.getName()};
    }
    
    @Override
    public String getDeleteActionURL() {
        return "manageAgendaController.json?action=delete";
    }

    @Override
    public String getDetailActionURL() {
        return "manageAgendaController.json?action=getById";
    }

    @Override
    public String getListActionURL() {
        return "manageAgendaController.json?action=list";
    }

}





Search filter

This component would serve as the filter attached to your grid. It would appear on top of your grid.

package javagoogleappspot.examples.web.client.forms;

import com.sixqos.gwt.client.filter.Filter;
import com.sixqos.gwt.client.filter.SearchForm;
import com.smartgwt.client.types.DSOperationType;
import com.smartgwt.client.types.VerticalAlignment;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.IButton;
import com.smartgwt.client.widgets.events.ClickEvent;
import com.smartgwt.client.widgets.events.ClickHandler;
import com.smartgwt.client.widgets.form.DynamicForm;
import com.smartgwt.client.widgets.form.fields.FormItem;
import com.smartgwt.client.widgets.form.fields.TextItem;
import com.smartgwt.client.widgets.form.fields.events.KeyPressHandler;
import com.smartgwt.client.widgets.grid.ListGrid;
import com.smartgwt.client.widgets.layout.HLayout;
import com.smartgwt.client.widgets.layout.LayoutSpacer;
import com.smartgwt.client.widgets.layout.VLayout;

public class AgendaEntrySearchForm extends SearchForm {

    ListGrid filterGrid;
    VLayout vlayout;
    
    public AgendaEntrySearchForm(Filter filter){
        super(filter);
    }
    
    @Override
    public void assemble() {
        filterGrid = getFilter().getGrid();
        vlayout = new VLayout();
        HLayout hLayout = new HLayout();
        
        DynamicForm form = getSearchForm();
        
        KeyPressHandler handler = new com.smartgwt.client.widgets.form.fields.events.KeyPressHandler(){            
            public void onKeyPress(com.smartgwt.client.widgets.form.fields.events.KeyPressEvent event){
                if (event.getKeyName().equals("Enter")){                    
                    getSearchForm().setSaveOperationType(DSOperationType.FETCH);        
                    filterGrid.invalidateCache();
                    filterGrid.fetchData(getSearchForm().getValuesAsCriteria());                    
                }
                
            }            
        };

        form.setHeight100();
        form.setWidth100();
        form.setPadding(5);
        form.setLayoutAlign(VerticalAlignment.BOTTOM);
        form.setNumCols(8);
        form.setDataSource(getFilter().getGrid().getDataSource());


        TextItem name = new TextItem("name");
        name.setTitle("Name");
        name.addKeyPressHandler(handler);
                    
        form.setFields(new FormItem[] { name});

        IButton searchButton = new IButton("Search");

        
        searchButton.addClickHandler(new ClickHandler() {
            public void onClick(ClickEvent event) {
                getSearchForm().setSaveOperationType(DSOperationType.FETCH);        
                filterGrid.invalidateCache();
                filterGrid.fetchData(getSearchForm().getValuesAsCriteria());                
            }
        });    

        
        IButton clearButton = new IButton("Clear");

        
        clearButton.addClickHandler(new ClickHandler() {
            public void onClick(ClickEvent event) {
                getSearchForm().reset();    
            }
        });    
        
        
        vlayout.addMember(form);
        LayoutSpacer spacer = new LayoutSpacer();
        spacer.setWidth100();
        hLayout.addMember(spacer);        
        hLayout.addMember(searchButton);        
        spacer = new LayoutSpacer();
        spacer.setWidth(20);
        hLayout.addMember(spacer);        
        hLayout.addMember(clearButton);        
        spacer = new LayoutSpacer();
        spacer.setWidth100();
        hLayout.addMember(spacer);        
        
        vlayout.addMember(hLayout);
    }


    @Override
    public Canvas getElement() {
        return vlayout;
    }

} 





Detail entity window

You need a detail window that will contain your entity's data. This detail window will be used for update and creation. Please provide in the implementation the json calls for persistence.

package javagoogleappspot.examples.web.client.forms;

import com.gwtent.client.reflection.Reflection;
import javagoogleappspot.examples.web.client.dto.AgendaEntryDTO;
import com.sixqos.gwt.client.filter.DetailForm;
import com.sixqos.gwt.client.filter.Filter;
import com.smartgwt.client.data.DSCallback;
import com.smartgwt.client.data.DSRequest;
import com.smartgwt.client.data.DSResponse;
import com.smartgwt.client.types.DSOperationType;
import com.smartgwt.client.types.VerticalAlignment;
import com.smartgwt.client.widgets.IButton;
import com.smartgwt.client.widgets.events.ClickEvent;
import com.smartgwt.client.widgets.events.ClickHandler;
import com.smartgwt.client.widgets.form.DynamicForm;
import com.smartgwt.client.widgets.form.fields.FormItem;
import com.smartgwt.client.widgets.form.fields.HiddenItem;
import com.smartgwt.client.widgets.form.fields.TextItem;
import com.smartgwt.client.widgets.layout.HLayout;
import com.smartgwt.client.widgets.layout.VLayout;

public class AgendaEntryDetail extends DetailForm {
    DynamicForm form;
    AgendaEntryDTO agendaEntryDTO;
    
    public String getAddActionURL(){ 
        return "manageAgendaController.json?action=create";
    }
    public String getWindowName(){
        return "Beneficiary";
    }
    public String getUpdateActionURL(){
        return "manageAgendaController.json?action=update";
    }

    public AgendaEntryDetail(Filter filer) {
        super(filer);
    }
    public AgendaEntryDetail(){
        super();
    }
    public void loadItems(Reflection... dto) {
        agendaEntryDTO = null;
        String buttonAction = "";
        if (dto!=null){        
            agendaEntryDTO = (AgendaEntryDTO) dto[0];
        }
        VLayout formWindow = new VLayout();
        formWindow.setWidth100();
        formWindow.setHeight100();
        formWindow.setMembersMargin(20);

        HLayout hLayout = new HLayout();

        form = new DynamicForm();
        form.setHeight100();
        form.setWidth100();
        form.setPadding(5);
        form.setLayoutAlign(VerticalAlignment.BOTTOM);
        HiddenItem id = new HiddenItem("id");

        TextItem firstName = new TextItem("firstName");
        firstName.setTitle("First name");

        TextItem lastName = new TextItem("lastName");
        lastName.setTitle("Last name");
        
        if (agendaEntryDTO != null) {
            firstName.setValue(agendaEntryDTO.getFirstName());
            lastName.setValue(agendaEntryDTO.getLastName());
            id.setValue(agendaEntryDTO.getId());
            buttonAction = "Update";
        }
        else{
            buttonAction = "Create";
            }

        form.setFields(new FormItem[] { id,firstName,lastName});
        IButton createButton = new IButton(buttonAction);

        createButton.addClickHandler(new ClickHandler() {

            public void onClick(ClickEvent event) {
                if (agendaEntryDTO == null)
                    form.setSaveOperationType(DSOperationType.ADD);
                else
                    form.setSaveOperationType(DSOperationType.UPDATE);
                form.saveData(new DSCallback() {
                    public void execute(DSResponse response, Object rawData,
                            DSRequest request) {
                        getWinModal().destroy();
                        getGrid().invalidateCache();
                        getGrid().fetchData(getFilter().getSearchForm().getSearchForm().getValuesAsCriteria());
                    }

                });
            }
        });
        form.setDataSource(getDatasource());
        formWindow.addMember(form);
        hLayout.addMember(createButton);
        formWindow.addMember(hLayout);
        getWinModal().addItem(formWindow);
    }
    
    public DynamicForm getForm() {
        return form;
    }
}





Our final result should look like this:






Define your html page


We need a main html page to serve as a starting point to load the compiled javascript.  Create the file Agenda.html, include the path to the compiled image of the javascript. Place the file under your war directory.


<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
   
    <title>Agenda</title>
   
    
    <!--add loading indicator while the app is being loaded-->
    <div id="loadingWrapper">
    <div id="loading">
        <div class="loadingIndicator">
            <span id="loadingMsg">Loading application ...</span></div>
    </div>
    </div>
    
    <!-- IMPORTANT : You must set the variable isomorphicDir to [MODULE_NAME]/sc/ so that the SmartGWT resource are 
      correctly resolved -->    
      
    <script> var isomorphicDir = "javagoogleappspot.examples.web.Init/sc/"; &lt/script> 
    
    <script type="text/javascript" language="javascript" src="javagoogleappspot.examples.web.Init/javagoogleappspot.examples.web.Init.nocache.js">
</script>
  </head>

  <body>

    <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>

  </body>
</html> 








Compile your code


Define a run configuration with as the main class having the following parameters :
Main class : com.google.gwt.dev.Compiler
Program arguments : -gen  "${path}/Agenda/war/www"  javagoogleappspot.examples.web.Init VM arguments : -Xmx512m -Xss1024k


Run in hosted mode

Define a run configuration with as the main class having the following parameters :

Main class : com.google.gwt.dev.GWTShell
Program arguments : -noserver  -port 8080 Agenda.html
VM arguments : -Xmx512m

Make sure to start your embedded GAP Jetty engine before, otherwise the hosted mode browser will not retrieve the initial page.

Deploy your application to Google App Engine



The application comes with the appengine-web.xml deployment descriptor. Make sure to exclude the redundant smartGWT compiled javascript  code, otherwise your deploy will fail.

appengine-web.xml :


<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
    <application>javagoogleappspotagenda</application>
    <version>1</version>
    
    <!-- Configure java.util.logging -->
    <system-properties>
        <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
    </system-properties>
    <sessions-enabled>true    
    <resource-files>
        <!-- Exclude smartgwt files from the list of resource files -->
        <exclude path="/gwtent/**.*" />
        <exclude path="/sc/**.*" />        
        <exclude path="/javagoogleappspot.examples.web.Init/sc/**.*" />
    </resource-files>   
</appengine-web-app>






Download the application source code here

The application has been deployed and runs under http://javagapagenda.appspot.com/


 

About Me

My Photo