April 22, 2009

Google Web Toolkit 和 Google App Engine 综合教程 交互篇

前面几篇教程已经把Google Web Toolkit 和 Google App Engine 两方面的代码完成了很大部分,这篇教程将让Google Web Toolkit 的客户端代码与 Google App Engine 的服务器端代码联合起来,实现客户端和服务器端的交互。

Google Web Toolkit 如何与服务器交互?

Google Web Toolkit 的程序最终会以JavaScript代码的形式在用户的浏览器上运行。所以,如果要与服务器交互,要使用JavaScript支持的方法。Google Web Toolkit 为我们提供了3种方法。

远程过程调用 (Remote Procedure Calls, GWT RPC)

如果项目的服务器端使用Java,并且为服务器端的操作都使用了各种接口,那么 GWT RPC是最好的选择。因为我们使用 Google App Engine 作为服务器端,使用Java编码,所以接下来将使用 GWT RPC来完成我们接下来的教程。 更详细的有关 Remote Procedure Calls 的介绍,请看这里

HTTP 取回 JSON

如果项目的服务器端没有使用Java,亦或是已经使用了JSON 或 XML,那么就可以通过HTTP来取得JSON来实现与服务器端的交互。 更详细的有关 JSON 的介绍,请看这里

利用 JSONP 协议

如果你对 mashup 很感兴趣,那么一定不能错过 Google Web Toolkit 提供的这种方法。 更详细的有关 JSONP 的介绍,请看这里

定义 Service Interface

RPC Service 要由一个 继承自 RemoteService 的接口来定义。这里我们在 net.kylewu.myideastorm.client.service包下面新建一个名为 DBWorkerService 的接口。

package net.kylewu.idea.client.service;
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
/*
* The client side stub for the RPC service.
*/
@RemoteServiceRelativePath("myidea")
public interface DBWorkerService extends RemoteService {

    // Remove one idea
    Boolean delete(Long id);

    String getIdeaBySubject(String subject);

    // Read from db
    String[] getIdeas();

    // Add a new idea
    String save(String subject, String detail, String progress);

    // Update one idea
    String update(String id, String subject, String detail, String progress);

}

肯定有人注意到了,这里方法返回的值都是String,为什么不使用我们之前定义过的Idea呢。这里我只能说很抱歉了,我测试过返回Idea对象,但是在运行时会出现 “did you forget to inherit a required module?”的错误,搜索了很久也没有搞定,希望知道的同学联系我,谢谢。

定义服务器端Service实现

服务器端的类要实现刚才写过的 DBWorkerService接口,在这里实现我们具体的操作。

package net.kylewu.idea.server;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import net.kylewu.idea.client.service.DBWorkerService;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

public class DBWorkerServiceImpl extends RemoteServiceServlet implements
DBWorkerService {

    private static final long serialVersionUID = 6183858098728558886L;

    @Override
    public Boolean delete(Long id) {
        PersistenceManager pm = PMF.get().getPersistenceManager();
        Idea idea = (Idea) pm.getObjectById(Idea.class, id);
        pm.deletePersistent(idea);
        pm.close();
        return true;
    }

    @SuppressWarnings("unchecked")
    @Override
    public String[] getIdeas() {
        PersistenceManager pm = PMF.get().getPersistenceManager();
        String query = "select from " + Idea.class.getName();
        List ideas = (List) pm.newQuery(query).execute();
        String[] rtn = new String[ideas.size()];
        for (int i = 0; i < ideas.size(); i++) {
            Idea idea = ideas.get(i);
            rtn[i] = idea.toString();
        }

        return rtn;
    }

    @Override
    public String save(String subject, String detail, String progress) {
        Idea idea = new Idea(subject, detail, progress,
                progress.compareTo("100%") == 0 ? new SimpleDateFormat("yyyy-mm-dd")
                        .format(new Date()) : "null");
        PersistenceManager pm = PMF.get().getPersistenceManager();
        try {
            pm.makePersistent(idea);
        } finally {
            pm.close();
        }
        String str = getIdeaBySubject(subject);
        return str;
    }

    @Override
    public String update(String id, String subject, String detail,
            String progress) {
        PersistenceManager pm = PMF.get().getPersistenceManager();
        Idea idea = (Idea) pm.getObjectById(Idea.class, Long.valueOf(id));
        idea.setSubject(subject);
        idea.setDetail(detail);
        idea.setProgress(progress);
        if (progress.compareTo("100%") == 0
                && idea.getDate().compareTo("null") == 0) {
            idea.setDate(new SimpleDateFormat("yyyy-mm-dd").format(new Date()));
        }
        pm.makePersistent(idea);

        String rtn = ((Idea) pm.getObjectById(Idea.class, Long.valueOf(id)))
                .toString();
        pm.close();

        return rtn;
    }

    @SuppressWarnings("unchecked")
    @Override
    public String getIdeaBySubject(String subject) {
        PersistenceManager pm = PMF.get().getPersistenceManager();
        Query query = pm.newQuery("select from " + Idea.class.getName()
                + " where subject == subjectParm "
                + "parameters String subjectParm");
        List ids = (List) query.execute(subject);
        if (ids.isEmpty() == false) {
            pm.close();
            return ids.get(0).toString();
        }
        pm.close();
        return "";
    }

}

如上一篇教程最后部分提到的,这里通过PMF得到PersistenceManager,进而进行数据库的增删改查。

定义 RPC Interface

代码很简单,在我们的DBWorkerService接口中的方法的参数最后添加一个AsyncCallback参数,并且这些方法都是void。

package net.kylewu.idea.client.service;
import com.google.gwt.user.client.rpc.AsyncCallback;
/*
* The async counterpart of DBWorker.
*/
public interface DBWorkerServiceAsync {

    // Remove one idea
    void delete(Long id, AsyncCallback callback);

    // Read from db
    void getIdeas(AsyncCallback callback);

    // Add a new idea
    void save(String subject, String summary, String progress, AsyncCallback callback);

    // Update one idea
    void update(String id, String summary, String subject, String progress, AsyncCallback callback);

    void getIdeaBySubject(String subject, AsyncCallback callback);

}

好了,到这里,就可以调用 RPC 接口了。

调用 RPC Interface

重新打开EntryPoint,在类中添加一段如下代码,然后在需要数据库操作的地方添加代码。

private DBWorkerServiceAsync dbWorker = GWT.create(DBWorkerService.class);

这里是最后完整的Kylewuidea类。

package net.kylewu.idea.client;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import net.kylewu.idea.client.service.DBWorkerService;
import net.kylewu.idea.client.service.DBWorkerServiceAsync;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
/*
* Entry point classes define onModuleLoad().
*/
public class Kylewuidea implements EntryPoint {

private final int COL_ID = 0;
private final int COL_SUBJECT = 1;
private final int COL_DETAIL = 2;
private final int COL_PROGRESS = 3;
private final int COL_TIME = 4;
private final int COL_OPERATION = 5;

private FlexTable table = new FlexTable();

private ArrayList subjectList = new ArrayList();

private Map mapStrToInt = new HashMap();
private Map mapIntToStr = new HashMap();

private DBWorkerServiceAsync dbWorker = GWT.create(DBWorkerService.class);

/**
 * This is the entry point method.
 */
public void onModuleLoad() {

    // Initial all items.
    init();
    // Add table to html page.
    RootPanel.get("ideastorm").add(createBasePanel());
    // Initial table.
    importFromDatabase();

}

private void init() {

    mapStrToInt.put("0%", 0);
    mapStrToInt.put("25%", 1);
    mapStrToInt.put("50%", 2);
    mapStrToInt.put("75%", 3);
    mapStrToInt.put("100%", 4);
    mapIntToStr.put(0, "0%");
    mapIntToStr.put(1, "25%");
    mapIntToStr.put(2, "50%");
    mapIntToStr.put(3, "75");
    mapIntToStr.put(4, "100%");

    // Initial table structure.
    table.setText(0, COL_ID, "ID");
    table.setText(0, COL_SUBJECT, "Subject");
    table.setText(0, COL_DETAIL, "Detail");
    table.setText(0, COL_PROGRESS, "Progress");
    table.setText(0, COL_OPERATION, "Operation");
    table.setText(0, COL_TIME, "Time");

    // Set table attribute.
    table.setCellPadding(5);
    table.getColumnFormatter().setWidth(0, "10");
    table.getColumnFormatter().setWidth(1, "200");
    table.getColumnFormatter().setWidth(2, "400");
    table.getColumnFormatter().setWidth(3, "150");
    table.getColumnFormatter().setWidth(4, "100");
}

/**
 * Initial table data
 */
private void importFromDatabase() {
    // Get exist ideas from db
    dbWorker.getIdeas(new AsyncCallback() {

        @Override
        public void onFailure(Throwable caught) {
            // TODO Exception

        }

        @Override
        public void onSuccess(String[] result) {
            for (String idea : result) {
                String[] parts = idea.split("\|");
                insertIdeaIntoTable(-1, parts[0], parts[1], parts[2],
                        parts[3], parts[4]);

            }
        }

    });

}

/**
 * Create base panel
 *
 * @return
 */
private Panel createBasePanel() {
    // Base Panel of this project.
    VerticalPanel mainPanel = new VerticalPanel();
    Button btnAdd = new Button("Add Idea");

    // Add click handler to add button.
    btnAdd.addClickHandler(new ClickHandler() {
        public void onClick(ClickEvent event) {
            // Show Add Idea Dialog
            showIdeaEditDialog(true, -1);
        }
    });

    // Assemble the panel.
    mainPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
    mainPanel.add(table);
    mainPanel.add(btnAdd);

    return mainPanel;
}

/**
 * Show Add Idea Dialog
 */
private void showIdeaEditDialog(final boolean isNew, final int index) {
    // Initial Add Idea Dialog.
    final DialogBox dialog = new DialogBox();
    final TextBox txtBoxSubject = new TextBox();
    final TextArea txtAreaDetail = new TextArea();
    final ListBox listBox = new ListBox();
    VerticalPanel dialogPanel = new VerticalPanel();
    HorizontalPanel itemPanel = new HorizontalPanel();
    Button btnInsert = new Button();
    Button btnClose = new Button("Close");

    // Set attribute.
    dialog.setText("Input your idea");
    dialog.setAnimationEnabled(true);
    txtAreaDetail.setSize("300", "380");
    listBox.clear();
    listBox.addItem("0%");
    listBox.addItem("25%");
    listBox.addItem("50%");
    listBox.addItem("75%");
    listBox.addItem("100%");
    listBox.setVisibleItemCount(5);

    if (isNew) {
        btnInsert.setText("Insert");
        txtBoxSubject.setText("Input your indea");
        listBox.setSelectedIndex(0);
    } else {
        btnInsert.setText("Update");
        txtBoxSubject.setText(table.getText(index, COL_SUBJECT));
        txtAreaDetail.setText(table.getText(index, COL_DETAIL));
        listBox.setSelectedIndex(mapStrToInt.get(table.getText(index,
                COL_PROGRESS)));
        if (table.getText(index, COL_PROGRESS).compareTo("100%") == 0  )
            listBox.setEnabled(false);
    }

    // Add ClickHandler to Insert button
    btnInsert.addClickHandler(new ClickHandler() {
        public void onClick(ClickEvent event) {
            // Check empty
            if (txtBoxSubject.getText().length() == 0
                    || txtAreaDetail.getText().length() == 0)
                return;
            // Check exist
            if (subjectList.contains(txtBoxSubject.getText()) == true
                    && isNew) {
                return;
            }

            insert(index, txtBoxSubject.getText(), txtAreaDetail.getText(),
                    mapIntToStr.get(listBox.getSelectedIndex()));
            dialog.hide();
        }

    });

    // Add ClickHandler to Close button
    btnClose.addClickHandler(new ClickHandler() {
        public void onClick(ClickEvent event) {
            dialog.hide();
        }
    });

    // Assemble dialog panel.
    itemPanel.setWidth("100%");
    itemPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
    itemPanel.add(listBox);
    itemPanel.add(btnInsert);
    itemPanel.add(btnClose);
    dialogPanel.add(txtBoxSubject);
    dialogPanel.add(txtAreaDetail);
    dialogPanel.add(itemPanel);

    // Associate the dialog with the panel.
    dialog.setWidget(dialogPanel);

    // Show dialog.
    dialog.center();
}

private void insert(final int index, final String subject,
        final String detail, final String progress) {
    if (index == -1) {
        // new idea
        dbWorker.save(subject, detail, progress,
                new AsyncCallback() {

                    @Override
                    public void onFailure(Throwable caught) {
                        // TODO Auto-generated method stub
                    }

                    @Override
                    public void onSuccess(String idea) {
                        if (idea.length() != 0) {
                            String[] parts = idea.split("\|");
                            insertIdeaIntoTable(index, parts[0], parts[1],
                                    parts[2], parts[3],
                                    parts[4] == "null" ? "" : parts[4]);
                        }
                    }

                });
    } else {
        dbWorker.update(table.getText(index, COL_ID), subject, detail,
                progress, new AsyncCallback() {

                    @Override
                    public void onFailure(Throwable caught) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void onSuccess(String idea) {
                        if (idea.length() != 0) {
                            String[] parts = idea.split("\|");
                            insertIdeaIntoTable(index, parts[0], parts[1],
                                    parts[2], parts[3],
                                    parts[4].compareTo("null")==0 ? "" : parts[4]);
                        }
                    }

                });
    }
}

private void insertIdeaIntoTable(int index, final String id,
        final String subject, String detail, String progress, String date) {
    //
    if (index == -1) {
        index = table.getRowCount();
        subjectList.add(subject);
    } else {
        subjectList.set(index - 1, subject);
    }

    HorizontalPanel panel = new HorizontalPanel();
    Button btnUpdate = new Button("Update");
    Button btnRemove = new Button("Remove");

    // Add handler to buttons
    btnUpdate.addClickHandler(new ClickHandler() {

        @Override
        public void onClick(ClickEvent event) {
            int i = subjectList.indexOf(subject);
            showIdeaEditDialog(false, i + 1);
        }

    });

    btnRemove.addClickHandler(new ClickHandler() {

        @Override
        public void onClick(ClickEvent event) {
            dbWorker.delete(Long.valueOf(id), new AsyncCallback() {

                @Override
                public void onFailure(Throwable caught) {
                    // TODO Auto-generated method stub

                }

                @Override
                public void onSuccess(Boolean result) {
                    int i = subjectList.indexOf(subject);
                    table.removeRow(i + 1);
                    subjectList.remove(subject);

                }
            });

        }

    });

    panel.add(btnUpdate);
    panel.add(btnRemove);

    table.setWidget(index, COL_OPERATION, panel);
    table.setText(index, COL_ID, id);
    table.setText(index, COL_SUBJECT, subject);
    table.setText(index, COL_DETAIL, detail);
    table.setText(index, COL_PROGRESS, progress);
    if (progress.compareTo("100%") == 0 && table.getText(index, COL_TIME).length() == 0) {
        table.setText(index, COL_TIME, date);
    }

}

}

Well done,来运行一下看效果吧。点击Add弹出对话框,填入新的idea,这样在table中就出现了这个idea,同时,会把idea插入服务器端的数据库。重新刷新一下界面,会再次从数据库读取信息,显示在table中。

演示地址 :http://kylewuidea.appspot.com

总结

好了,就写到这里了,稍微回顾一下,在熟悉了Google Web Toolkit 和 Google App Engine 后,我们先制作了界面,然后实现了服务器端的数据存储,今天把前后台结合起来。难度很低,因为是一个入门教程,也是自我学习的一个记录。还有很多东西没有去实现,比如现在任何人都可以Add Idea ( 这个其实只要在server端判断一下user就可以了 ),对重复的idea没有进行检查,样式上几乎没做任何操作…… 希望诸位同学多多指导,大家都来做些好的应用。