歡迎您光臨本站 註冊首頁

使用 Grails 構建富 Internet 應用程序,第 2 部分: Grails 和 G

←手機掃碼閱讀     火星人 @ 2014-03-12 , reply:0
  
在這個共 2 部分的系列的第 2 部分中,將基於您在 第 1 部分 中用 Grails 創建的 Web 服務創建新的服務。您將創建一個新的搜索頁面,但這一次使用 Google Web Toolkit (GWT) 來創建這個應用程序。此外,您還將使用 Ext GWT 庫中的一些更豐富的 UI 小部件。

關於本系列

這個系列探索一些應用程序架構,它們在後端使用由 Grails 框架實現的面向服務的體系架構(SOA)。了解 Grails 如何大大簡化了 Web 應用程序的創建,尤其是 Web 服務的創建。這種後端可以輕鬆連接到任意純客戶端應用程序。在第 1 部分中,您將使用 Adobe Flex 創建一個可以使用 Flash Player 的應用程序。在本文中,您將通過 Google Web Toolkit (GWT) 用純 JavaScript 創建前端。

先決條件

在這篇文章中,您將使用 Grails 和 GWT 構建一個 Web 應用程序。Grails 框架基於 Groovy 編程語言,這是針對 Java™ 平台的動態語言。熟悉 Groovy 會更好,但不是必要的。了解 Java 或其他動態語言(比如 Ruby 或 Python)會有很大幫助。本文使用 Grails 1.0.3(參見 參考資料)。Grails 可用於許多資料庫或應用伺服器,但本文不需要提供它們,因為它們已經隨 Grails 附帶。這個前端是使用 Google Web Toolkit 構建的,並且您必須熟悉 Java 才能使用 GWT。本文使用的是 Google Web Toolkit 1.5.3(參見 參考資料)。





新服務

在第 1 部分中,您使用 Grails 創建了一個使用 Service Oriented Architecture (SOA) 的應用程序。您可以基於這個後端構建一個新的應用程序。這是使用具有純客戶端前端的 SOA 後端的主要優勢。您的伺服器端代碼完全從用戶界面分離出來。不過,為了進一步演示使用 Grails 創建 Web 服務有多麼簡單,我們將修改現有的後端,並為其添加一個搜索 API。

搜索 API 服務

現有的應用程序已有一個搜索業務服務。我們僅使用它的列表 API 為應用程序中的所有新聞提供一個列表。看看這個服務的其他功能,如清單 1 所示。


清單 1. 搜索業務服務
				  class SearchService {        boolean transactional = false        def list() {          Story.list()      }        def listCategory(catName){          Story.findAllWhere(category:catName)      }            def searchTag(tag){          Story.findAllByTagsIlike("%"+tag+"%")      }  }  

listCategory 方法查找特定類別中的所有新聞。searchTag 方法查找帶有特定標記的所有新聞。它不要求精確匹配,也不區分大小寫。如前所述,到目前為止我們僅使用列表 API。現在我們創建一個使用這兩個方法的新 Web 服務。您僅需將一個新方法添加到 ApiController,如清單 2 所示。


清單 2. 新的 ApiController
				  import grails.converters.*    class ApiController {      // injected services      def searchService      def storyService        def search = {          def results= null          def tagResults = null          if (params.tag){              tagResults = searchService.searchTag(params.tag)          }          def catResults = null          if (params.category){              catResults = searchService.listCategory(params.category)          }          if (params.tag && params.category){              def tagMap = [:]              tagResults.each{ story ->                  tagMap[story.id] = story              }              results = catResults.findAll { tagMap[it.id] != null}           } else {              if (params.category){                  results = catResults              } else {                  results = tagResults              }           }          render results as JSON      }  }  

清單 2 僅演示了新的搜索方法。它需要兩個請求參數:tag 和 category。它能夠分別或同時處理這兩個參數。像 Grails 應用程序中的所有請求參數一樣,這兩個參數是通過 params 對象公開的。如果存在 tag 參數,那麼將在搜索服務中調用 searchTag 方法。如果存在 category 參數,那麼將在搜索服務中調用 listCategory 方法。如果兩個參數都具備的話(比如 params.tag 和 params.category),那麼將變得很有趣。

我們只希望顯示調用搜索服務時返回的兩個新聞列表中共有的新聞。所以首先要使用 [:] 符號創建一個空映射。這是一個來自 Java 的 HashMap。然後循環遍歷列表獲得帶有特定標記的新聞,並以每條新聞的 ID 為鍵將它們放入映射中。對每個閉包使用標準的 Groovy 語法。然後您就可以在分類搜索中看到所有其 ID 出現在您創建的映射中的新聞。使用 Groovy 添加到列表的便捷的 findAll 方法,並且再次使用閉包。這次將對閉包使用 Groovy 的簡化符號(‘it’ 對象)。最後,不管結果如何,來自 Grails 的 JSON 轉換器會將該列錶轉換成一個 JSON 對象。

在上一篇文章中(見 參考資料),您看到了通過 Grails 將數據呈現為 XML 有多麼容易。同樣,Grails 也可以很方便地將數據呈現為 JSON。現在,您可以測試新的 Web 服務。根據 Grails 約定,已知 Web 服務的 URL 為 http://<root>/digg/api/search。現在給出一個示例輸出,這是搜索類別為 “technology” 的內容得到的結果,如清單 3 所示。


清單 3. 示例搜索輸出
				  [{"id":1,"class":"Story","category":"technology","description":"How to get   a ternary operator in Scala","link":"http://blog.tmorris.net/does-scala-have-  javas-ternary-operator/","tags":"programming scala","title":"Does Scala have   Java's ternary operator?","votesAgainst":0,"votesFor":0},{"id":3,"class":"Story",  "category":"technology","description":"New animations available in the Flex 4 'Gumbo'   release.","link":"http://graphics-geek.blogspot.com/2008/10/flex-specificanimations  -posted.html","tags":"flash flex","title":"Flex Specific Animations Posted",  "votesAgainst":0,"votesFor":0}]  

這正是您期望看到的標準 JSON。這是一個對象數組,其中每個 JSON 對象表示一條新聞。比較有趣的是,Grails 序列化程序(serializer)添加了一個 class 屬性,該屬性是您的對象的 Groovy 類。您的應用程序現在還不需要它,因為列表具有相同的作用,但在某些場景中這個元數據是非常有用的。現在已經修改了 Web 服務,可以開始使用 GWT 構建一個新的使用該服務的應用程序。





使用 GWT 編寫應用程序

我們將構建一個新的應用程序,以從 Digg clone 應用程序中搜索新聞。就像基於 Flex 的應用程序一樣,這個應用程序將是一個能夠調用 Grails 服務的客戶端應用程序。不過,這一次使用的是 GWT。這允許您用 Java 編寫所有應用程序,但 GWT 將其編譯成在客戶機上運行的非常高效的 JavaScript。現在我們看看用 GWT 編寫的應用程序的入口點。

搜索入口點

GWT 應用程序的入口點是一個 Java 類,它的已編譯 JavaScript 是針對特定頁面的腳本。它負責創建用戶界面和處理交互。在本例中,您需要創建一個用戶可以填寫的表單,以指定搜索查詢和這些查詢的結果。看看清單 4 中的代碼。


清單 4. DiggApp 類
				  public class DiggApp implements EntryPoint {        private static final String[][] CATEGORIES = {          { "Technology", "technology" }, { "World & Business", "business" },          { "Science", "science" }, { "Gaming", "games" },          { "Lifestyle", "lifestyle" }, { "Entertainment", "entertainment" },          { "Sports", "sports" }, { "Offbeat", "miscellaneous" } };        private final HorizontalPanel searchPanel = createSearchForm();      private final VerticalPanel resultsPanel = new VerticalPanel();        public void onModuleLoad() {          RootPanel.get().add(searchPanel);          RootPanel.get().add(resultsPanel);      }      private HorizontalPanel createSearchForm() {          HorizontalPanel panel = new HorizontalPanel();          panel.setTitle("Search for Stories");          Label tagLabel = new Label("Tag:");          final TextBox tagBox = new TextBox();          panel.add(tagLabel);          panel.add(tagBox);          Label catLabel = new Label("Category:");          final ListBox catBox = new ListBox();          catBox.addItem("");          for (String[] category : CATEGORIES){              catBox.addItem(category[0],category[1]);          }          panel.add(catLabel);          panel.add(catBox);          Button searchBtn = new Button("Search");          searchBtn.addClickListener(new ClickListener(){              public void onClick(Widget sender) {                  search(tagBox.getText(), catBox.getValue(catBox.getSelectedIndex()));              }          });          panel.add(searchBtn);          return panel;      }  }  

當載入頁面時將調用 onModuleLoad 方法。在本例中,它將創建一個搜索表單,該表單有一個用於輸入標記的文本框,一個用於選擇類別的下拉列表,以及一個用於請求搜索的搜索按鈕。它還創建一個空面板,用於顯示搜索結果。單擊搜索按鈕將調用搜索方法。清單 5 給出了搜索方法的代碼。


清單 5. DiggApp 搜索方法
				  private final void search(String tag, String category){      resultsPanel.clear();      Story.search(tag, category, new RequestCallback(){          public void onError(Request request, Throwable exception) {              Label label = new Label("Sorry there was an error");              resultsPanel.add(label);          }          public void onResponseReceived(Request request, Response response) {              List<Story> stories = Story.fromJson(response.getText());              SearchTable grid = new SearchTable(stories);              resultsPanel.add(grid);          }      });  }      

代碼對 Story 類使用了搜索方法。Story 類用作系統中的模型,它非常類似於本系列第 1 部分在 Flex 應用程序中創建的 Story 類。它有一個靜態搜索方法,並充當一個數據模型。該類的代碼如清單 6 所示。


清單 6. Story 類
				  public class Story {        private static final String BASE_URL = "/digg/api/search?";            private int id;      private String link;      private String title;      private String description;      private String tags;      private String category;      private int votesFor;      private int votesAgainst;        public Story(JSONObject obj){          id = (int) obj.get("id").isNumber().doubleValue();          link = obj.get("link").isString().stringValue();          title = obj.get("title").isString().stringValue();          description = obj.get("description").isString().stringValue();          tags = obj.get("tags").isString().stringValue();          category = obj.get("category").isString().stringValue();          votesFor = (int) obj.get("votesFor").isNumber().doubleValue();          votesAgainst = (int) obj.get("votesAgainst").isNumber().doubleValue();      }            public static void search(final String tag, final String category, final   RequestCallback callback){          String url = buildRequest(tag, category);          RequestBuilder builder =               new RequestBuilder(RequestBuilder.GET, url);          try {              builder.sendRequest(null, new RequestCallback(){                  public void onError(Request request, Throwable exception) {                      callback.onError(request, exception);                  }                  public void onResponseReceived(Request request, Response response) {                      callback.onResponseReceived(request, response);                  }                              });          } catch (RequestException e) {              callback.onError(null, e);          }      }  }  

注意,這個清單僅是該類的一部分;省略了一些 helper、getter 和 setter 函數。搜索方法接受參數 tag 和 category,以及一個 RequestCallback。它然後使用 GWT RequestBuilder 類創建一個 HTTP 請求併發送到 Web 服務。RequestCallback 是 GWT 中的一個介面,用於發送非同步 HTTP 請求。Story.search 方法委託傳入到其內部的 RequestCallback。因此,在這裡它將執行在 清單 5 中創建的 RequestCallback。注意,不管是清單 5 還是 清單 6,我們都創建了一個用於實現 RequestCallback 介面的匿名內部類。這是 GWT(和一般的 Java UI 編程)中常用的技巧,因為它使您可以訪問包含的類中的欄位和方法。我們再回過頭看看 清單 5,可以看到使用 Story.fromJson 方法獲取響應文本,然後將其解析成 Story 對象的列表。如清單 7 所示。


清單 7. Story.fromJson 方法
				  public static List<Story> fromJson(String jsonText){      JSONValue val = JSONParser.parse(jsonText);      JSONArray array = val.isArray();      List<Story> stories = new ArrayList<Story>(array.size());      for (int i=0;i<array.size();i++){          Story story = new Story(array.get(i).isObject());          stories.add(story);      }      return stories;  }  

這個方法使用了 GWT 的 JSONParser 類。它創建一個 JSON 對象數組,並將每個對象傳遞到 Story 類的構造器中(如 清單 6 所示)。回過頭來看看 清單 5,可以看到,獲得新聞對象列表之後,您就會使用它創建一個 SearchTable 對象。這是一個定製的小部件,如清單 8 所示。


清單 8. SearchTable 小部件
				  public class SearchTable extends FlexTable {      private List<Story> stories;      public SearchTable(List<Story> stories) {          super();          this.stories = stories;          this.buildTable();      }      private void buildTable(){          this.setBorderWidth(2);          this.setText(0, 0, "story");          this.setText(0, 1, "category");          this.setText(0, 2, "description");          if (stories.size() == 0){              showMessage("Sorry there were no results");          } else {              for (int i=0;i<stories.size();i++){                  Story story = stories.get(i);                  setWidget(i+1, 0, story.getTitleLink());                  setText(i+1, 1, story.getCategory());                  setText(i+1, 2, story.getDescription());              }          }      }      private void showMessage(String msg){          setText(1,0, msg);          getFlexCellFormatter().setColSpan(1, 0, 3);      }      }  

這個類擴展了核心的 GWT 類 FlexTable。它僅循環遍歷新聞列表,然後填充一個簡單的表。如果沒有新聞,它將顯示一個簡單的說明消息。現在,您已經創建了應用程序所需的全部代碼,但還有一件事情需要完成。Story 類使用 GWT 的 HTTP 庫和 JSON 庫,以向 Web 服務發送請求並解析從服務中返回的響應。您必須將它添加到應用程序的模型定義中,讓編譯器可以使用它。如清單 9 所示。


清單 9. 模型定義
				  <module>          <!-- 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.standard.Standard'/>        <!-- <inherits name='com.google.gwt.user.theme.chrome.Chrome'/> -->        <!-- <inherits name='com.google.gwt.user.theme.dark.Dark'/>     -->          <!-- Other module inherits                                      -->        <inherits name="com.google.gwt.http.HTTP" />        <inherits name="com.google.gwt.json.JSON" />          <!-- Specify the app entry point class.                         -->        <entry-point class='org.developerworks.digg.client.DiggApp'/>              <!-- Specify the application specific style sheet.              -->        <stylesheet src='DiggApp.css' />  </module>  

這只是由 GWT applicationCreator 腳本生成的文件,但您向它添加了兩行代碼。這兩行代碼在清單 9 中的 ‘Other module inherits’ 註釋後面。您添加的是兩個 GWT 庫:HTTP 和 JSON。現在已經準備好將應用程序編譯成 JavaScript 並部署它。





GWT 部署

在 Java Web 應用程序中出現 GWT 代碼是很常見的,因此將它們合併到一個 WAR 文件。在本例中,您僅使用 GWT 的客戶端部分 — 換句話說,僅使用了 HTML、CSS 和 JavaScript。這使您能夠獨立使用 GWT,而不依賴於伺服器技術。您僅需將 Java 編譯成 JavaScript,然後將其與其他 Web 應用程序合併。

當您使用 GWT 的 applicationCreator 腳本創建 GWT 應用程序時,它將自動為您創建一個編譯器腳本。它是一個可執行程序,因此調用它很簡單,如清單 10 所示。


清單 10. 編譯 GWT 應用程序
				  $ ./DiggApp-compile  Compiling module org.developerworks.digg.DiggApp  2008-11-08 19:56:14.962 java[1300:c1b]         [Java CocoaComponent compatibility mode]: Enabled  2008-11-08 19:56:14.963 java[1300:c1b] [Java CocoaComponent compatibility mode]:         Setting timeout for SWT to 0.100000  Compilation succeeded  Linking compilation into ./www/org.developerworks.digg.DiggApp  

現在需要將這個文件夾(org.developerworks.digg.DiggApp)複製到 Web 應用程序。還記得嗎,GWT 是作為 JavaScript 運行的,因此 GWT 應用程序向遠程伺服器發出的任何調用都受到同源策略的限制。將 GWT 應用程序部署為 Grails 應用程序的一部分是極其簡單的。為此,僅需將它複製到 Grails 應用程序的 web-app 文件夾下,如圖 1 所示。


圖 1. 部署到 Grails 的 GWT 應用程序

現在,您可以在瀏覽器中打開並運行新的搜索應用程序。根據 Grails 約定,URL 應該為 http://<root>/digg/org.developerworks.digg.DiggApp/DiggApp.html。它類似於圖 2。


圖 2. 搜索應用程序

圖 2 中的應用程序僅顯示了搜索表單。輸入一個標記或類別(或同時輸入兩者)來執行搜索。結果如圖 3 所示。


圖 3. 帶有結果的搜索應用程序

這就是完整的應用程序!這是具有核心組件的典型 GWT 應用程序。這些組件提供一個基於標準 HTML 的瘦包裝器。這使應用程序速度變快,並且是輕量級的,但是仍然有一些遜色,至少難以與大部分基於 JavaScript 的小部件工具(或 Flex 應用程序)相提並論。幸運的是,它具有一些選項,即 Ext GWT。





使用 Ext GWT 小部件

Ext JS 庫是一個用於創建應用程序的流行 JavaScript 庫。它具有大量很好的小部件,以及用於常見 JavaScript 任務的實用方法。此外,還可以將它轉換成 Ext GWT 形式的 Java,以和 Google Web Toolkit 一起使用。您首先需要將應用程序配置為可以使用 Ext GWT。

設置 Ext GWT

設置 Ext GWT 很簡單。首先,Ext GWT JAR(gxt.jar,Ext GWT 下載的一部分)必須在您的類路徑中。對於這個應用程序,您可以創建一個 lib 目錄,然後將 gxt.jar 複製到其中。現在需要將它添加到編譯器的類路徑,因此要通過將 ‘./lib/gxt.jar’ 添加到類路徑來修改 DiggApp-compile 腳本,如清單 11 所示。


清單 11. 修改後的 DiggApp-compile 腳本
				  #!/bin/sh  APPDIR=`dirname $0`;  java -XstartOnFirstThread -Xmx256M -cp "$APPDIR/src:$APPDIR/bin:  /Users/michael/lib/gwt-mac-1.5.3/gwt-user.jar:  /Users/michael/lib/gwt-mac-1.5.3/gwt-dev-mac.jar:./lib/gxt.jar"   com.google.gwt.dev.GWTCompiler -out "$APPDIR/www" "$@"   org.developerworks.digg.DiggApp;  

接下來,需要稍微修改一下 GWT 生成的基本 HTML 頁面。該頁面可以在 /public 目錄中找到。修改後的版本如清單 12 所示。


清單 12. 修改後的 DiggApp.html
				  <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">      <html>    <head>      <meta http-equiv="content-type" content="text/html; charset=UTF-8">      <!--                                           -->      <!-- Any title is fine                         -->      <!--                                           -->      <title>DiggApp</title>            <!--                                           -->      <!-- This script loads your compiled module.   -->      <!-- If you add any GWT meta tags, they must   -->      <!-- be added before this line.                -->      <!--                                           -->      <script type="text/javascript" language="javascript"           src="org.developerworks.digg.DiggApp.nocache.js"></script>      <link rel="stylesheet" type="text/css" href="css/ext-all.css" />    </head>      <!--                                           -->    <!-- The body can have arbitrary html, or      -->    <!-- you can leave the body empty if you want  -->    <!-- to create a completely dynamic UI.        -->    <!--                                           -->    <body>        <!-- OPTIONAL: include this if you want history support -->      <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1'           style="position:absolute;width:0;height:0;border:0"></iframe>      </body>  </html>  

這裡做了兩處修改。首先,將 DOCTYPE 改成 HTML 3.2,這是 Ext 要求的。其次,添加了 Eex 所需的 CSS 樣式表:“css/ext-all.css”。注意,您不需要從其他地方複製這個 CSS 文件;當編譯器發現應用程序使用 Ext GWT 模塊時,它將創建一個 CSS 文件。最後一處修改是:將 Ext GWT 模塊添加到模塊定義中。如清單 13 所示。


清單 13. 帶有 GXT 支持的模塊定義
				  <module>          <!-- 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.standard.Standard'/>        <!-- <inherits name='com.google.gwt.user.theme.chrome.Chrome'/> -->        <!-- <inherits name='com.google.gwt.user.theme.dark.Dark'/>     -->          <!-- Other module inherits                                      -->        <inherits name="com.google.gwt.http.HTTP" />        <inherits name="com.google.gwt.json.JSON" />     <inherits name='com.extjs.gxt.ui.GXT'/>           <!-- Specify the app entry point class.                         -->        <entry-point class='org.developerworks.digg.client.DiggApp'/>              <!-- Specify the application specific style sheet.              -->        <stylesheet src='DiggApp.css' />  </module>  

只要比較一下清單 13 和 清單 9,您就會發現它們之間僅有一處不同。現在這個模塊繼承自 com.extjs.gxt.ui.GXT 模塊。這意味著已經配置了 Ext GWT,因此可以使用 Ext 的小部件。

更豐富的搜索結果

Ext 包含一個比 GWT 表更豐富的 Grid 小部件。它的許多功能與 Flex 中的 DataGrid 相同,比如可排序的、可調整大小的列。您將使用它作為一個新的用於顯示搜索結果的小部件的基礎,如清單 14 所示。


清單 14. StoryGrid 小部件
				  public class StoryGrid extends LayoutContainer {        public StoryGrid(List<Story> stories){          this.setLayout(new FlowLayout(10));          this.setSize(750, 300);          ListStore<BaseModelData> store = this.buildDataModel(stories);          Grid<BaseModelData> grid =               new Grid<BaseModelData>(store, createColumnModel());          grid.setBorders(true);          add(grid);      }            private ColumnModel createColumnModel(){          List<ColumnConfig> configs = new ArrayList<ColumnConfig>();          ColumnConfig column = new ColumnConfig();          column.setId("titleLink");          column.setHeader("Story");          column.setWidth(200);          configs.add(column);          column = new ColumnConfig();          column.setId("category");          column.setHeader("Category");          column.setWidth(100);          configs.add(column);          column = new ColumnConfig();          column.setId("description");          column.setHeader("Description");          column.setWidth(400);          configs.add(column);          return new ColumnModel(configs);      }            private ListStore<BaseModelData> buildDataModel(List<Story> stories){          ListStore<BaseModelData> data = new ListStore<BaseModelData>();          for (Story story : stories){              BaseModelData model = new BaseModelData(story.properties());              data.add(model);          }          return data;      }  }  

Ext 中的 Grid 需要具備兩個基礎元素:列定義和數據存儲。createColumnModel 方法為 Grid 創建列定義。它是一個 ColumnConfig 對象列表。ColumnConfig 中比較關鍵的地方是它的 ID 屬性。這將把要使用的數據模型的屬性名告訴 Grid。數據模型由 buildDataModel 方法創建。您會用到 GXT 類 BaseModelData。這個類僅包圍在 Java Map 周圍。在本例中,您使用的是 Story 類的 properties() 方法,如清單 15 所示。


清單 15. Story.properties 方法
				  public class Story {          public Map<String,Object> properties(){          Map<String,Object> props = new HashMap<String,Object>();          props.put("id",id);          props.put("link", link);          props.put("title", title);          props.put("description",description);          props.put("tags",tags);          props.put("category",category);          props.put("votesFor",votesFor);          props.put("votesAgainst",votesAgainst);          props.put("titleLink", getTitleLink().getHTML());          return props;      }  }  

如果您現在回過頭來看看 清單 14 中在 createColumnModel 方法中創建的 ColumnConfig 對象,就會發現當把 ColumnConfig 的 ID 設置為 “titleLink” 時,那麼該字元串將被用作進入 清單 15 中創建的 Map 的鍵。現在將 SearchTable 替換為 清單 5 中的 StoryGrid,然後重新編譯並部署它。現在,應用程序有些變化,如圖 4 所示。


圖 4. 改進后的搜索應用程序

您還可以進一步改善 UI。例如,可以為 Grid 添加更多的列。您可以定製哪些列可以用於存儲,哪些列可以調整其大小,以及哪些列可以訪問定製菜單。您甚至還可以使用其他 Ext 小部件替換搜索表單組件。可以擴展的餘地非常大!





結束語

在本系列的第 1 部分,您使用 Grails 和 Flex 分別創建一個 SOA 後端和一個 UI。在這篇文章中,您為這個後端添加了一個新的 Web 服務。Grails 和 Groovy 再一次使這個過程變得非常簡單。您使用 GWT 創建一個新的搜索應用程序,並且看到 GWT 如何通過它的 HTTP 和 JSON 庫與 Web 服務一起使用。最後,您將 Ext GWT 作為向 GWT 添加豐富的 UI 小部件的一種方式。現在您用純 Java 創建了一個特性豐富的應用程序,它被編譯成 JavaScript 運行在客戶機上。(責任編輯:A6)



[火星人 ] 使用 Grails 構建富 Internet 應用程序,第 2 部分: Grails 和 G已經有686次圍觀

http://coctec.com/docs/linux/show-post-68903.html