ExtGWT 初学进阶教程


第一章:初识 ExtGWT 在本章中,我要介绍一下什么是 ExtGWT, 并且解释说明她是如何和 GWT(google)协 同工作的。然后会告诉大家如何搭建第一个 Ext GWT 项目。 概要:  在 Eclipse 上搭建 Ext GWT 环境  创建一个 GWT 项目  在 GWT 项目里使用 Ext GWT  用 Ext GWT 的组件改写 GWT 实例程序 到底 GWT 还缺少啥? 做为 java 程序员,去开发富客户端的 web 程序是很头疼的一件事。我们需要掌握深厚的 web 前端技术,诸如“Javascript,AJAX, css”此类,更别说还需要处理不同浏览器之间的差 异了。Google 公司早就发现这一问题,就这样 Google WebToolkit(GWT)就孕育而生了。 她致力于让 java 程序员用 java 面向对象开发习惯,去开发 web 界面应用(类似与 java Applet)。尽管如此,GWT 工具包有些异于通常的项目开发架构的需要,就大多数项目而 言,使用 GWT 只能作为一个局部的解决方案,而不是整个的。 解开 GWT 的神秘面纱,她只是一组比较基本的控件集。这远远不能够满足开发人员的需要, 去搭建一个企业级的应用。所幸,GWT 是一个开源的并且扩展性强的项目。为了弥补她的 不足,有不少与之相关的项目孕育而生,ExtGWT 就是她们其中之一。 ExtGWT 给我们提供什么? 借助于 GWT 优势,使用 ExtGWT 开发出来的项目,可以让开发商给他们的用户近类似于 桌面应用程序的体验。 ExtGWT 提供了类似于桌面开环境的扩展组件包,GWT 的程序员可以轻松的使用他们就像 使用 GWT 组件一样。除此之外,ExtGWT 还具有强大的本地操作和远程调用的特性,同时 满足开发企业级应用程序的 MVC 架构需求。 在 GWT 项目里面加入 ExtGWT(GXT) 对于任何一个 GWT 项目来说,加入 GXT 只需要简单的附加类库既可。如果我们现在现有 一个 GWT 项目,那么我仅仅需要做的就是如下三步:  从 Sencha 官网上,下载 GXT SDK。  在 GWT 项目里配置引用相关 GXT 类库。  拷贝 GXT 自带的 resource 目录到现有的项目中 如果我们没有现成的 GWT 项目,也不要紧。现在我们就从头开始 GXT。 准备工作 在开始我的 GXT 之旅之前,我首先要下载 GWT,搭建 GWT 开发环境。下面是我们主要的 所需列表:  Eclipse IDE for JavaEE Developers 3.6(http://www.eclipse.org/downloads/)  Ext GWT 2.2.0 SDKfor GWT 2.0 SDK (http://www.sencha.com/products/extgwt/)  GWT 的建立过程参见 (http://code.google.com/eclipse/docs/getting_started.html) Google 提供 了非常有用的开发 GWT 的 EclipsePlugin,具体插件安装过程就不赘述了。 GXT 准备工作 下载好 GXTSDK 包之后,我们解压缩后,开始配置 Eclipse:  打开 Eclipse,点击 Window |Preferences  在左边的列表中,点击 Java |Build Path | User Libraries  新建一个 User Libraries,命名为 GXT_2_2_5  加入 GXT 的 jar 包,选择适合自己当前 GWT 版本的 jar 包 创建第一个 GWT 项目 项目环境已经搭建好了,接下来我们先创建一个 GWT 项目,然后在此基础上进入 GXT 应 用。  首先,点击 File | New| Project  在弹出的向导里面,选择 Google 目录里面的 Web Application Project  输入项目名称和 package 基目录(我没有用那么高的版本),按照截图勾选 相关的选项。  点击 Finish,就会成功创建一个默认的 GWT 项目。按照截图运行程序  双击生成的 url,就会在浏览器上显示出来默认功能页面  最好安装 Chrome 浏览器,使用 Firefox 的话不要太高的版本。因为第一次运 行 GWT 程序的时候浏览器会下载插件,GWT 的插件在高版本的 Firefox 会 上安装失效。大家会注意到 url 后面跟的另外一个端口地址,那个是用来调用 插件功能的。它会和 jetty 服务器通讯,帮你调试 degug,编译 GWT 程序自 动发布 js,方便开发。jetty 是 google 自带的一个服务器,也可以使用 tomcat 来跑。 在 tomcat 上跑起来 jetty 是 google 自带的一个服务器,不用安装,Eclipse 插件安装好后,就可以运行。我不 知道她是否有容器可以支撑 spring,hibernate 等,也不知道随着项目不断壮大能否负载的了。 经过反复测试,小妹我自己总结出来如何让 GWT 的项目既可以在 jetty 上跑,也可以在 tomcat 上跑。(但是当运行在 tomcat 时,client 端的代码没办法跟断点 debug)  新建一个 Dynamic Web Project  项目创建完毕后,右键项目,点击 properties。给项目引入 GWT,如截图  此时的项目还不是一个 GWT 项目,因为还没有相关的 GWT 配置文件,要配 置 GWT 的 Entry Point Modules(类似于 flex 的程序入口文件指定)项目才 可以运行,我们可以把先前生成项目里面的 package 和 java code 都拷贝过 来先。如下截图  注意 0:编辑 FirstApp 项目的 web.xml 文件,删除 FirstApp1 项目。然后把 FirstApp 项目中凡是“FirstApp1”字样的“1”都给去掉。下面是几个主要文件修 改后的结果 FirstApp.gwt.xml 文件,Eclipse 的 GWT 项目,主要通过这个文件进行编译,来规定程序的 入口文件 [html] view plaincopyprint? 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. web.xml 文件修改后的内容如下 [html] view plaincopyprint? 1. 2. 6. FirstApp 7. 8. 9. greetServlet 10. com.danielvaughan.firstapp.server.GreetingServiceImpl 11. 12. 13. 14. greetServlet 15. /firstapp/greet 16. 17. 18. 19. 20. FirstApp.html 21. 22.  注意 1:Java build Path 里面的 tomcat 类库一定要在最下面,否则项目无法 运行。我分析是 tomcat 的类库的 jar 包和引入的 GWT:gwt-servlet.jar 冲突。 所以从项目在加载 jar 的顺序上做处理,让项目先加载 gwt-servlet.jar,然后再 加载 tomcat 相关的 jar 包,这样就可以避免冲突。  注意 2:用 jetty 运行时,她会自动帮你编译。用 tomcat 运行时,则需要你手 动的编译 GWT。编译成功后,会在 WebContent 目录下生成 firstapp 目录, 里面存放一些编译后的 js 等文件  注意 3:启动 tomcat 前要配置勾选“Public module contexts to separate XML files”,否则项目发布不成功。  注意 4:tomcat 成功启动程序后,断点 debug 只能跟踪 server 包下的类,而 client 包下是没办法跟踪断点的。没办法只能同时启动 jetty。我测试的 url 是 (http://127.0.0.1:8080/FirstApp/FirstApp.html?gwt.codesvr=127.0.0.1:999 7)这样任何目录下都可以跟踪断点了:)。运行效果如下 在 GWT 项目开始真正的 GXT 的之旅 之前我们都是开始搭建一个 GWT 项目,如何编译,运行她,在 jetty 和 tomcat 上。以及一 些需要注意的问题。现在我们需要把这个项目用 GXT 做一下修改。 步骤如下:  导入 GXT 类库: 找到 FirstApp 项目,右键,进入 Java Build Path,引入 GXT-2.2.5 类库。 并且把 gxt-.2.25-gwt22.jar 拷贝到 FirstApp\WebContent\WEB-INF\lib 里面以保证项目运行 环境。  在 GWT module 配置里,加入 GXT 的 entry: GWT module 文件(FirstApp.gwt.xml),她配置了 GWT 程序的入口配置,以及所引用的 一些额外的使用类库。她始终以“gwt.xml”文件名结尾,并且放在项目的基包 (com.danielvaughan.firstapp/FirstApp.gwt.xml)里面。为了使用 GXT,我们需要修改里 面的内容。 默认情况下,GWT 项目引用她自带的样式包()。现在我们要使用 GXT 的样式 包(),修改后的 FirstApp.gwt.xml 文件如下: [html] view plaincopyprint? 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22.  拷贝 GXT SDK 目录里面的 resources 文件夹到项目 WebContent 下,然后 重命名为 gxt,然后整个项目的结构目录如下:  修改 FirstApp.html 文件,引用 GXT 的 css [html] view plaincopyprint? 1.  注意此时还不能运行项目,因为默认项目是使用 GWT 的控件来完成页面渲 染,我们目前仅仅引入了 GXT 的 css,并没有使用 GXT 的控件  下一节我们会 FirstApp 项目里面的 GWT 控件替换为 GXT 控件,到那时大家 就会看到同一个功能页面,展示的不用页面渲染效果哦~~~~ 旧貌换新颜 现在,我们 FirstApp 项目已经引入了 GXT 库,但是我们还没有具体使用他们。现在我们从 FirstApp.java 拷贝出一份文件,重命名为 FirstGxtApp.java。在整个文件里,我们将使用 GXT 的控件去替换 GWT 的控件。通过之间的比较,我们会发现他们之间很相似,但是也 有不同,下面跟我小妹的脚步吧:  找到 FirstApp.java,拷贝出一份文件,重命名为 FirstGxtApp.java  删除一些 import [html] view plaincopyprint? 1. import com.google.gwt.event.dom.client.ClickEvent; 2. import com.google.gwt.event.dom.client.ClickHandler; 3. import com.google.gwt.event.dom.client.KeyUpEvent; 4. import com.google.gwt.event.dom.client.KeyUpHandler; 5. import com.google.gwt.user.client.ui.Button; 6. import com.google.gwt.user.client.ui.DialogBox; 7. import com.google.gwt.user.client.ui.Label; 8. import com.google.gwt.user.client.ui.TextBox; 9. import com.google.gwt.user.client.ui.VerticalPanel;  导入一些相应的 GXT [java] view plaincopyprint? 1. import com.extjs.gxt.ui.client.event.ButtonEvent; 2. import com.extjs.gxt.ui.client.event.SelectionListener; 3. import com.extjs.gxt.ui.client.event.KeyListener; 4. import com.extjs.gxt.ui.client.event.ComponentEvent; 5. import com.extjs.gxt.ui.client.widget.Dialog; 6. import com.extjs.gxt.ui.client.widget.Label; 7. import com.extjs.gxt.ui.client.widget.VerticalPanel; 8. import com.extjs.gxt.ui.client.widget.button.Button; 9. import com.extjs.gxt.ui.client.widget.form.TextField; 你会发现 GXT 和 GWT 之间有不少类似的类,如下是一些他们之间的对比情况 GXT GWT com.extjs.gxt.ui.client.widget.Dialog com.google.gwt.user.client.ui.DialogBox com.extjs.gxt.ui.client.widget.Label com.google.gwt.user.client.ui.Label com.extjs.gxt.ui.client.widget.VerticalPanel com.google.gwt.user.client.ui.VerticalPanel com.extjs.gxt.ui.client.widget.button.Button com.google.gwt.user.client.ui.Button com.extjs.gxt.ui.client.widget.form.TextField com.google.gwt.user.client.ui.TextBox com.extjs.gxt.ui.client.event.ButtonEvent com.google.gwt.event.dom.client.ClickEvent com.extjs.gxt.ui.client.event.SelectionListener com.google.gwt.event.dom.client.ClickHandler com.extjs.gxt.ui.client.event.KeyListener com.google.gwt.event.dom.client.KeyUpEvent com.extjs.gxt.ui.client.event.ComponentEvent com.google.gwt.event.dom.client.KeyUpHandler  接下来,我们要做的,就是在 FirstGXTApp.java 中,重新用 GXT 类库,来 定义控件。在 GWT 例子(FirstApp.java)里面,所有的控件代码集都是定义在 onModuleLoad()方法里面,然后使用的内部类去实现 Handler(interface),来 处理不同的事件。但是 GXT 使用 listeners(class)去处理事件的,因此在处理 方式上,我们需要把 GXT 的控件提出到 onModuleLoad()方法之外,变成属 性去处理。 [java] view plaincopyprint? 1. private final Button sendButton = new Button("Send"); 2. private final TextField nameField = new 3. TextField(); 4. private final Dialog dialogBox = new Dialog(); 5. private final Label textToServerLabel = new Label(); 6. private final HTML serverResponseLabel = new HTML();  GXT 和 GWT 的控件之间,在 functions 上,存在这一些不同。在开始修改之 前,先看看我总结的一些不同点。 GXT GWT TextField.setValue() TextBox.setText() TextField.focus() TextBox.setFocus(true) DialogBox.setHeading() DialogBox.setText() DialogBox.setAnimCollapse(true) DialogBox.setAnimationEnabled(true) VerticalPanel .setHorizontalAlign(HorizontalAlignment.RIGHT); VerticalPanel.setHorizontalAlignment(VerticalPanel.ALIGN_RIGHT)  另外一个不同点,就正如我上面提到的,就是事件处理机制不同。GWT 使用 event handlers,GXT 是使用 event listeners(类似于早期版本的 GWT)。但 是不管怎么样,代码风格上也非常类似。下面我把整个两个文件贴出来,大 家看看不同之处 FirstApp.java [java] view plaincopyprint? 1. package com.danielvaughan.firstapp.client; 2. 3. import com.danielvaughan.firstapp.shared.FieldVerifier; 4. import com.google.gwt.core.client.EntryPoint; 5. import com.google.gwt.core.client.GWT; 6. import com.google.gwt.event.dom.client.ClickEvent; 7. import com.google.gwt.event.dom.client.ClickHandler; 8. import com.google.gwt.event.dom.client.KeyCodes; 9. import com.google.gwt.event.dom.client.KeyUpEvent; 10. import com.google.gwt.event.dom.client.KeyUpHandler; 11. import com.google.gwt.user.client.rpc.AsyncCallback; 12. import com.google.gwt.user.client.ui.Button; 13. import com.google.gwt.user.client.ui.DialogBox; 14. import com.google.gwt.user.client.ui.HTML; 15. import com.google.gwt.user.client.ui.Label; 16. import com.google.gwt.user.client.ui.RootPanel; 17. import com.google.gwt.user.client.ui.TextBox; 18. import com.google.gwt.user.client.ui.VerticalPanel; 19. 20. /** 21. * Entry point classes define onModuleLoad(). 22. */ 23. public class FirstApp implements EntryPoint { 24. /** 25. * The message displayed to the user when the server cannot be reached or 26. * returns an error. 27. */ 28. private static final String SERVER_ERROR = "An error occurred while " 29. + "attempting to contact the server. Please check your network " 30. + "connection and try again."; 31. 32. /** 33. * Create a remote service proxy to talk to the server-side Greeting service. 34. */ 35. private final GreetingServiceAsync greetingService = GWT 36. .create(GreetingService.class); 37. 38. /** 39. * This is the entry point method. 40. */ 41. public void onModuleLoad() { 42. final Button sendButton = new Button("Send"); 43. final TextBox nameField = new TextBox(); 44. nameField.setText("GWT User"); 45. final Label errorLabel = new Label(); 46. 47. // We can add style names to widgets 48. sendButton.addStyleName("sendButton"); 49. 50. // Add the nameField and sendButton to the RootPanel 51. // Use RootPanel.get() to get the entire body element 52. RootPanel.get("nameFieldContainer").add(nameField); 53. RootPanel.get("sendButtonContainer").add(sendButton); 54. RootPanel.get("errorLabelContainer").add(errorLabel); 55. 56. // Focus the cursor on the name field when the app loads 57. nameField.setFocus(true); 58. nameField.selectAll(); 59. 60. // Create the popup dialog box 61. final DialogBox dialogBox = new DialogBox(); 62. dialogBox.setText("Remote Procedure Call"); 63. dialogBox.setAnimationEnabled(true); 64. final Button closeButton = new Button("Close"); 65. // We can set the id of a widget by accessing its Element 66. closeButton.getElement().setId("closeButton"); 67. final Label textToServerLabel = new Label(); 68. final HTML serverResponseLabel = new HTML(); 69. VerticalPanel dialogVPanel = new VerticalPanel(); 70. dialogVPanel.addStyleName("dialogVPanel"); 71. dialogVPanel.add(new HTML("Sending name to the server:")); 72. dialogVPanel.add(textToServerLabel); 73. dialogVPanel.add(new HTML("
Server replies:")); 74. dialogVPanel.add(serverResponseLabel); 75. dialogVPanel.setHorizontalAlignment(VerticalPanel.ALIGN_RIGHT); 76. dialogVPanel.add(closeButton); 77. dialogBox.setWidget(dialogVPanel); 78. 79. // Add a handler to close the DialogBox 80. closeButton.addClickHandler(new ClickHandler() { 81. public void onClick(ClickEvent event) { 82. dialogBox.hide(); 83. sendButton.setEnabled(true); 84. sendButton.setFocus(true); 85. } 86. }); 87. 88. // Create a handler for the sendButton and nameField 89. class MyHandler implements ClickHandler, KeyUpHandler { 90. /** 91. * Fired when the user clicks on the sendButton. 92. */ 93. public void onClick(ClickEvent event) { 94. sendNameToServer(); 95. } 96. 97. /** 98. * Fired when the user types in the nameField. 99. */ 100. public void onKeyUp(KeyUpEvent event) { 101. if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) { 102. sendNameToServer(); 103. } 104. } 105. 106. /** 107. * Send the name from the nameField to the server and wait for a response. 108. */ 109. private void sendNameToServer() { 110. // First, we validate the input. 111. errorLabel.setText(""); 112. String textToServer = nameField.getText(); 113. if (!FieldVerifier.isValidName(textToServer)) { 114. errorLabel.setText("Please enter at least four characters"); 115. return; 116. } 117. 118. // Then, we send the input to the server. 119. sendButton.setEnabled(false); 120. textToServerLabel.setText(textToServer); 121. serverResponseLabel.setText(""); 122. greetingService.greetServer(textToServer, 123. new AsyncCallback() { 124. public void onFailure(Throwable caught) { 125. // Show the RPC error message to the user 126. dialogBox 127. .setText("Remote Procedure Call - Failure"); 128. serverResponseLabel 129. .addStyleName("serverResponseLabelError"); 130. serverResponseLabel.setHTML(SERVER_ERROR); 131. dialogBox.center(); 132. closeButton.setFocus(true); 133. } 134. 135. public void onSuccess(String result) { 136. dialogBox.setText("Remote Procedure Call"); 137. serverResponseLabel 138. .removeStyleName("serverResponseLabelError"); 139. serverResponseLabel.setHTML(result); 140. dialogBox.center(); 141. closeButton.setFocus(true); 142. } 143. }); 144. } 145. } 146. 147. // Add a handler to send the name to the server 148. MyHandler handler = new MyHandler(); 149. sendButton.addClickHandler(handler); 150. nameField.addKeyUpHandler(handler); 151. } 152. } FirstGXTApp.java [java] view plaincopyprint? 1. package com.danielvaughan.firstapp.client; 2. 3. import com.extjs.gxt.ui.client.Style.HorizontalAlignment; 4. import com.extjs.gxt.ui.client.event.ButtonEvent; 5. import com.extjs.gxt.ui.client.event.ComponentEvent; 6. import com.extjs.gxt.ui.client.event.KeyListener; 7. import com.extjs.gxt.ui.client.event.SelectionListener; 8. import com.extjs.gxt.ui.client.widget.Dialog; 9. import com.extjs.gxt.ui.client.widget.Label; 10. import com.extjs.gxt.ui.client.widget.VerticalPanel; 11. import com.extjs.gxt.ui.client.widget.button.Button; 12. import com.extjs.gxt.ui.client.widget.form.TextField; 13. import com.google.gwt.core.client.EntryPoint; 14. import com.google.gwt.core.client.GWT; 15. import com.google.gwt.event.dom.client.KeyCodes; 16. import com.google.gwt.user.client.rpc.AsyncCallback; 17. import com.google.gwt.user.client.ui.HTML; 18. import com.google.gwt.user.client.ui.RootPanel; 19. 20. /** 21. * Entry point classes define onModuleLoad(). 22. */ 23. public class FirstGXTApp implements EntryPoint { 24. /** 25. * The message displayed to the user when the server cannot be reached or 26. * returns an error. 27. */ 28. private static final String SERVER_ERROR = "An error occurred while " 29. + "attempting to contact the server. Please check your network " 30. + "connection and try again."; 31. 32. 33. /** 34. * Create a remote service proxy to talk to the server-side Greeting 35. * service. 36. */ 37. private final GreetingServiceAsync greetingService = GWT 38. .create(GreetingService.class); 39. 40. private final Dialog dialogBox = new Dialog(); 41. private final VerticalPanel dialogVPanel = new VerticalPanel(); 42. private final TextField nameField = new TextField(); 43. private final Button sendButton = new Button("Send"); 44. private final HTML serverResponseLabel = new HTML(); 45. private final Label textToServerLabel = new Label(); 46. 47. /** 48. * This is the entry point method. 49. */ 50. public void onModuleLoad() { 51. 52. nameField.setValue("GWT User"); 53. 54. // We can add style names to widgets 55. sendButton.addStyleName("sendButton"); 56. 57. // Add the nameField and sendButton to the RootPanel 58. // Use RootPanel.get() to get the entire body element 59. RootPanel.get("nameFieldContainer").add(nameField); 60. RootPanel.get("sendButtonContainer").add(sendButton); 61. 62. // Focus the cursor on the name field when the app loads 63. nameField.focus(); 64. nameField.selectAll(); 65. 66. // Create the popup dialog box 67. 68. dialogBox.setHeading("Remote Procedure Call"); 69. dialogBox.setAnimCollapse(true); 70. 71. dialogVPanel.addStyleName("dialogVPanel"); 72. dialogVPanel.add(new HTML("Sending name to the server:")); 73. dialogVPanel.add(textToServerLabel); 74. dialogVPanel.add(new HTML("
Server replies:")); 75. dialogVPanel.add(serverResponseLabel); 76. dialogVPanel.setHorizontalAlign(HorizontalAlignment.RIGHT); 77. dialogBox.setButtons(Dialog.CLOSE); 78. dialogBox.add(dialogVPanel); 79. 80. Button closeButton = dialogBox.getButtonById(Dialog.CLOSE); 81. // Add a handler to close the DialogBox 82. closeButton.addSelectionListener(new SelectionListener() { 83. @Override 84. public void componentSelected(ButtonEvent ce) { 85. dialogBox.hide(); 86. sendButton.setEnabled(true); 87. sendButton.focus(); 88. } 89. }); 90. 91. sendButton.addSelectionListener(new SelectionListener() { 92. /** 93. * Fired when the user clicks on the sendButton. 94. */ 95. @Override 96. public void componentSelected(ButtonEvent ce) { 97. sendNameToServer(); 98. } 99. }); 100. 101. // Add a handler to send the name to the server 102. nameField.addKeyListener(new KeyListener() { 103. /** 104. * Fired when the user types in the nameField. 105. */ 106. @Override 107. public void componentKeyUp(ComponentEvent event) { 108. if (event.isSpecialKey(KeyCodes.KEY_ENTER)) { 109. sendNameToServer(); 110. } 111. } 112. }); 113. } 114. 115. /** 116. * Send the name from the nameField to the server and wait for a response. 117. */ 118. private void sendNameToServer() { 119. sendButton.setEnabled(false); 120. String textToServer = nameField.getValue(); 121. textToServerLabel.setText(textToServer); 122. serverResponseLabel.setText(""); 123. greetingService.greetServer(textToServer, new AsyncCallback() { 124. public void onFailure(Throwable caught) { 125. // Show the RPC error message to the user 126. dialogBox.setHeading("Remote Procedure Call - Failure"); 127. serverResponseLabel.addStyleName("serverResponseLabelError"); 128. serverResponseLabel.setHTML(SERVER_ERROR); 129. dialogBox.show(); 130. dialogBox.center(); 131. Button closeButton = dialogBox.getButtonById(Dialog.CLOSE); 132. closeButton.focus(); 133. } 134. 135. public void onSuccess(String result) { 136. dialogBox.setHeading("Remote Procedure Call"); 137. serverResponseLabel.removeStyleName("serverResponseLabelError"); 138. serverResponseLabel.setHTML(result); 139. dialogBox.show(); 140. dialogBox.center(); 141. Button closeButton = dialogBox.getButtonById(Dialog.CLOSE); 142. closeButton.focus(); 143. } 144. }); 145. } 146. }  两个文件修改好了之后,我们要修改 GWT 的 module 文件。打开 FirstApp.gxt.xml,修改 entry-point 为新的 FirstGXTApp。然后我们运行一下 吧 [html] view plaincopyprint? 1. 2. 第二章:GXT 组件 现在我们已经有了开发环境,在本章中,我要详细介绍 ExtGWT 组件用法,并且通过开发 一个 demo 程序来展示一系列的 GXT 组件。一开始会介绍一些关键的概念,然后就会着眼 于 demo 项目的实战开发。 本章,我们会涉及到如下 GXt 功能集:  Component  Container  BoxComponent  ScrollContainer  LayoutContainer  FlowLayout  ContentPanel  Viewport  BorderLayout  Loading messages  Custom Components  Buttons  Tooltip  Popup  SelectionListener  TextField  KeyListener 下图,是我们本章所涉及的控件集的层次结构。 ExtGwt Demo 库 在 Sencha 官网上,我们可以看到所有的 GXT 的 Demo (http://www.sencha.com/examples/explorer.html)。如果大家还没有预览,请先通览一下, 然后再开始我们的 demo 项目。 GXT 基础 1:Component GWT 中,Widget(com.google.gwt.user.client.ui.Widget)是所有显式组件的基类,正如我 们可以在浏览器中看见的:buttons, textboxes, tree views GXT 中,Component(com.extjs.gxt.ui.client.widget.Component)则是所有显式组件的基 类。因为 GXT 是建立在 GWT 之上的,所以我们也不会奇怪,所有 GXT 的 componests 是 建立在 GWT 的 Widget 之上的。具体说来,GXT components 的父类是 GXT 的 Widget, 并扩展了一些新的功能而已。 所有的 GXT 的 components 都引入了生命周期的概念,他们是:创建,附加,自动分离, 异步渲染。Components 继承了基本的 hide and show, enable and disable 功能。 BoxComponent 所有的 GXt 显式组件,都继承了 Component,同时也直接或间接的使用了 BoxComponent。 异步渲染 GWT的工作原理,就是操作 DOM 元素——Document Object Model 来渲染浏览器中的 html 页面。GWT widget 其实就是由 html 标记集合组成的,可以在 DOM 中添加和删除。 例如,一个 GWT 的 button widget 的 html 标记会被表述成,如下: [html] view plaincopyprint? 1. 在 GWT 中,当一个 widget 被初始化后,html 标记会被同时的渲染在 DOM 中。当一个 widget 被添加到另一个 GWT panel 的 widget 上时,也是同时的 html 标记被渲染。 GXT 的 components(就相对于 GWT 的 widgets)则被渲染的方式是不同的——异步渲染 (懒渲染)。当一个 GXT component 被初始化后,html 标记不会马上被渲染。只有当 component 的渲染方法被调用之后,html 标记才会被渲染。这种方法被看作是更有效的去 节省 html 所占用的内存空间。 虽然 GWT 的 component 是异步渲染的,但是 components 的相关属性是可以在渲染之前 就被设置的。例如,一个 TextField 在被 html 渲染之前,我们就可以给它设置一个 value 属 性。 如果另外一种情况,就是 GXT 的 componment 被添加到另外一个 GWT 的 panel 上时, component 的渲染方法则会马上被调用,也就是说会被同时的渲染该 component。如果换 成是一个 GXT 的 panel 里,添加了也是 GXt 的 component 的时候,那么他还是被异步渲 染的。 GXT 基础 2:Container Container 是 Boxcomponent 的一个子类,它可以包含其他的 components。类路径是: com.extjs.gxt.ui.client.widget.Container。他们只是负责附加,分离和管理他们的子 components,就 Container 自身而言,他不负责去处理 components 的布局摆放和渲染。 下面是从左到右的类继承关系。 LayoutContainer Layoutcontainer 继承了 ScrollContainer, ScrollContainer 的父类就是 Container。顾名思 义,ScrollContainer 可以使其内容在 Container 里是可折叠的。LayoutContainer 则多具备 一项功能,可以让子 components 的布局摆放效果交给一个具体的 Layout 类来处理。 让我通过如下一个简单例子,来了解异步渲染是如果工作的。  首先我们创建一个 LayoutContainer [java] view plaincopyprint? 1. LayoutContainer layoutContainer = new LayoutContainer();  此时,还没有任何 html 的渲染,当前的 layoutcontainer 也没有被添加到 GXT 容器和 GWT 的 Panel 中。下面我们添加一个 Button [java] view plaincopyprint? 1. LayoutContainer layoutContainer = new LayoutContainer(); 2. Button button = new Button("Click me");  同样,我们虽然创建了两个对象 layoutContainer 和 button,仍然还是没有任 何的 html 标记被渲染。下面将 button 添加到 layoutContainer 中 [java] view plaincopyprint? 1. LayoutContainer layoutContainer = new LayoutContainer(); 2. Button button = new Button("Click me"); 3. layoutContainer.add(button);  同样的,此时此刻仍然没有任何的 html 被渲染出来,这是因为 layoutContainer 他本身没有被渲染。如果我们将 layoutContainer 添加到 GWT 的 Panel 中(RootPanel),让我们拭目以待 [java] view plaincopyprint? 1. LayoutContainer layoutContainer = new LayoutContainer(); 2. Button button = new Button("Click me"); 3. layoutContainer.add(button); 4. RootPanel.get().add(layoutContainer);  添加 layoutContainer 到 RootPanel 中,会触发 layoutContainer 的渲染方法 被调用。Container 中的所有控件会有一种级联效应,被层级的渲染出来。因 此,当 layoutContainer 被渲染之后,他的 children 们的渲染方法也会被调用, 在这里就是刚刚创建的那个 button 的渲染方法会随之被调用。当我们通过工 具查看 DOM 的时候,我们会找到到 layoutContainer 和 button 对应的 html 标记  如果我们像再添加一个 button,如下代码: [java] view plaincopyprint? 1. LayoutContainer layoutContainer = new LayoutContainer(); 2. Button button = new Button("Click me"); 3. layoutContainer.add(button); 4. RootPanel.get().add(layoutContainer); 5. 6. Button anotherButton = new Button("Click me too"); 7. layoutContainer.add(anotherButton);  如果你认为 anotherButton 也会显式出来,那么你就想错了!当 layoutContainer 已经被渲染之后,再在 layoutContainer 身上添加的 anotherButton 不会被渲染了。对于这种情况,我们还有别的解决办法,就是 调用 layoutContainer 的 layout()方法,那么系统会自动的去调用两个 button 的渲染方法。 [java] view plaincopyprint? 1. LayoutContainer layoutContainer = new LayoutContainer(); 2. Button button = new Button("Click me"); 3. layoutContainer.add(button); 4. RootPanel.get().add(layoutContainer); 5. Button anotherButton = new Button("Click me too"); 6. layoutContainer.add(anotherButton); 7. layoutContainer.layout();  此时,anotherButton 的 html 标记已经被添加到 DOM 中了。 FlowLayout FlowLayout 是 LayoutContainer 的默认布局排列(layout)方式。这种 layout 方式下,所添 加的 components,都不能够被设置其摆放位置,大小等功能。只是每个 component 会伴 随着前一个 component 的渲染,而从左到右,从上到下的排列。之后我们会了解到更多的 layout 方式。 ContentPanel ContentPanel 是 LayoutContainer 的一个子类。这是个非常有用的,利于用户交互的控件。 稍后我们会使用到它。他由页眉,页脚和内容区三部分组成,在顶端和底端都可以显式工具 栏。它就有内建的折叠按钮和用来自定义功能的按钮。 GXT 基础 3:Events Events 概念是指:当有事件发生时,通知应用程序作出响应的这一过程。点击 button 或者 控件的状态改变,都可以当作一个事件,然后通过应用程序,使用户和程序之间产生某种互 动的效果。用户在控件上的每一个行为,每一个操作,都会导致某些事件的触发,如果这个 控件自身监听到了某些事件的发生,那么就会跳转到对应的处理流程中,以此作为事件的响 应。 从专业的角度来说,就像总所周知的 observer 模式那样。在 GXT 里,使用 listeners 来处 理事件的。如果一个 listener 已经被添加到某个 component 上,当某个 event 被触发后, 那么与之对应的 listener 就会被唤醒,然后处理该 event。 所有 Events 的基类是:com.extjs.gxt.ui.client.event.BaseEvent。并且 GXT 提供了广泛的 事件类型。稍后我们会在 demo 应用程序中逐渐的使用他们。 事件的 sink 和 swallow 作为 GWT 设计的一部分,widgets 只能响应浏览器中部分的事件,GXT 也是如此。原因是 降低内存使用量,避免内存使用溢出。如果让一个 widget 可以响应一个浏览器里的事件, 要调用 sinkEvents()方法把该事件注册。例如,默认的一个 component 可以响应 onClick event, 但是不支持响应 onDoubleClick。为此,我们需要通过 sinkEvents()方法在此控件 上组册此双击事件。 类似的方法,我们也可以销毁某些事件,来避免一个事件被多次触发。例如,如果像让一个 button component 的 onClick event 失效,我们可以注销调他。 Demo 应用程序-RSSReader 下面我们就开始一步一步的搭建一个 demo 程序,此 demo 会贯穿全文,我们会不断的优化 她,通过此过程深入了解 GXT 的各种应用。首先呢,我需要先新建一个最基本的项目。 需求:假如我们的客户有这样一个简单的需求。创建一个 RSS 新闻订阅读器,可以管理用 户指定的各式各样的 RSS;此程序必须要可以通过 web 访问,显式效果必须类似于桌面的 应用程序。不需要强制用户安装某一特定的浏览器,不需要用户设置显示器分辨率,要尽可 能的让我们的程序灵活起来。 解决方案:GWT 提供最优话的跨浏览器 JavaScript 解决方案,这正好满足了综上需求。在 此基础上,我们再使用 GXT,可以让程序展现的更像桌面程序。 项目背景:我们现在需要创建一个新的 GXT 项目,按照第一章所讲的步骤,此次项目命名 为 RSSReader。同时我们不需要 GWT 的默认生成的代码,要清楚干净他们。  创建项目基包:com.danielvaughan.rssreader  在此包下创建 GWT modal 文件 (com.danielvaughan.rssreader.RSSReader.gwt.xml) [java] view plaincopyprint? 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23.  创建新包(client),并在此包下创建类 (com.danielvaughan.rssreader.client.RSSReader),实现 onModuleLoad() 方法。 [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client; 2. 3. import com.google.gwt.core.client.EntryPoint; 4. 5. /** 6. * Entry point classes define onModuleLoad(). 7. */ 8. public class RSSReader implements EntryPoint { 9. 10. /** 11. * This is the entry point method. 12. */ 13. @Override 14. public void onModuleLoad() { 15. 16. } 17. }  编辑 web.xml 文件,设置欢迎页面 [html] view plaincopyprint? 1. 2. 6. RSSReader 7. 8. 9. RSSReader.html 10. 11.  WebContent/目录下创建 RSSReader.css 空文件。  创建欢迎页面(RSSReader.html);引入 GXT resources 目录里面的 css 文件和刚才建立的 RSSReader.css;引入 rssreader.nocache.js(GWT 编译 后会自动生成在 WebContent/目录下,注意大小写);把标签里内容 清空。 [html] view plaincopyprint? 1. 2. 3. 4. 5. 6. 7. RSSReader 8.
    10. 
11.
    12. 

13.
    14. 

15.
    16.
  • 搭建后的空项目,我们启动后访问应该是一个空白页面。整个目录结构如下
17.

18.
    19.
  • 下一节,会在这个 RSSReader 项目上开始 GXT 之旅。请大家拭目以待哦~~~
    20.
Viewport Viewport 的父类是 LayoutContainer,它会自动的填充整个浏览器的窗口,并且会监听窗口 的大小变化,随之变化。于此同时,也会负责处理其里面的子 components 在新改变窗口后 的大小。对于去展现一个类似于桌面应用程序来说,Viewport 是一个非常有用的 component。 我们会使用 Viewport,作为整个项目的基础面板。因此,它会被直接的添加到 GWT 的 root panel 上。不需要我们调用其 layout()方法,viewport 就会自动的伴随着 root panel 的渲染 而被渲染出来。  找到 RSSReader.java 类中的 onModuleLoad()方法,这是程序的入口方法。 我们创建一个 Viewport,然后添加到 GWT 的 RootPanel 里: [java] view plaincopyprint? 1. public void onModuleLoad() { 2. Viewport viewport = new Viewport(); 3. RootPanel.get().add(viewport); 4. }  此时我们运行浏览器,会仍然看到空白页面。那是因为 Viewport 目前没有应 用任何 css 样式。所以在 RSSReader.css 文件中加入如下 css 样式,Viewport 会自动的应用此样式。 [css] view plaincopyprint? 1. .x-viewport 2. { 3. background-color: #070; 4. }  .x-viewport 样式会自动的应用到 GXT Viewport 控件中,我们定义的是绿色 背景,所以当我们启动程序时,浏览器会随着 javaScript 的执行,Viewport 的渲染,而从白色变成绿色。 Layout Layout 类定义了 Layoutcontainer 里面 components 是如何布局和显式的。类路径是: com.extjs.gxt.ui.client.widget.Layout。稍后的章节会逐步使用到他们,这一章呢,我们先从 BorderLayout 开始学习。 BorderLayout 如果我们想展示一个全屏的应用程序,就一定要使用 BorderLayout。对于一个应用程序来 说,会被各种 components 充斥起来,但是他们也不会被胡乱的摆放。通常一个完整屏幕会 被划分为中心区域,其周围由东,南,西,北包围着。BorderLayout 就是为此布局设计的, 用户可以随意的改变每个区域的大小,伸缩和隐藏。 这种布局对于开发应用系统来说非常常见。North 区域用来作为页眉,South 区域作为页脚, Center 区域最为具体显式的内容,West 或 East 最为导航区。 BorderLayoutData 在使用 BorderLayout 添加子 component 到父 component 之前,我们要首先使用 BorderLayoutData 对象来定义 component 是如何展现的。 具体来说,就是我们需要通过创建 BorderLayoutData 对象,才可以设置其布局的区域 (North,South,West,East,Center),初始化面积,用户是否可以折叠他,以及最大 和最小面积等选项。 当我们定义好了一个具体的 BorderLayoutData 对象之后,我们才可以使用他去添加一个 component,应用到一个 BorderLayout 中去。通过下面的步骤,一看便知:  找到 RSSReader.java 类中的 onModuleLoad()方法,新建一个 BorderLayout 布局对象,然后让 Viewport 应用此布局。(大家一定要有个概念,就是控件 和布局效果是分开的,需要创建各自的对象,然后再让一个控件去应用一个 布局) [java] view plaincopyprint? 1. public void onModuleLoad() { 2. Viewport viewport = new Viewport(); 3. final BorderLayout borderLayout = new BorderLayout(); 4. viewport.setLayout(borderLayout); 5. RootPanel.get().add(viewport); 6. }  然后,创建一个 BorderLayoutData 对象,并设置他的布局位置,高度,以及 是否可以折叠功能。 [java] view plaincopyprint? 1. BorderLayoutData northData = new BorderLayoutData(LayoutRegion.NORTH,20); 2. northData.setCollapsible(false); 3. northData.setSplit(false);  新建一个 HTML widget(一提到 widget 就是 GWT 的东东,一提到 component 就是 GXT 的东东),最为整个页面的页眉(就是放在 North 区域)。 [java] view plaincopyprint? 1. HTML headerHtml = new HTML(); 2. headerHtml.setHTML("

RSS Reader

"); 3. viewport.add(headerHtml, northData);  再分别创建两个 BorderLayoutData,分别负责 Center 和 west 区域的布局效 果。West 区最为导航菜单来说,当然是可以折叠的。同时对其宽度做具体的 设置。 [java] view plaincopyprint? 1. BorderLayoutData centerData = new BorderLayoutData(LayoutRegion.CENTER); 2. centerData.setCollapsible(false); 3. BorderLayoutData westData = new BorderLayoutData(LayoutRegion.WEST, 200, 150, 300); 4. westData.setCollapsible(true); 5. westData.setSplit(true);  最后要创建两个 components(ContentPanel),分别对应,上面创建的两个 BorderLayoutData。 [java] view plaincopyprint? 1. ContentPanel mainPanel = new ContentPanel(); 2. ContentPanel navPanel = new ContentPanel(); 3. viewport.add(mainPanel, centerData); 4. viewport.add(navPanel, westData);  运行后的效果如下(怎么样,是不是看到有点像模像样啦 ):  把完整的代码贴出来,给大家分享一下: [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client; 2. 3. import com.extjs.gxt.ui.client.Style.LayoutRegion; 4. import com.extjs.gxt.ui.client.widget.ContentPanel; 5. import com.extjs.gxt.ui.client.widget.Viewport; 6. import com.extjs.gxt.ui.client.widget.layout.BorderLayout; 7. import com.extjs.gxt.ui.client.widget.layout.BorderLayoutData; 8. import com.google.gwt.core.client.EntryPoint; 9. import com.google.gwt.user.client.ui.HTML; 10. import com.google.gwt.user.client.ui.RootPanel; 11. 12. /** 13. * Entry point classes define onModuleLoad(). 14. */ 15. public class RSSReader implements EntryPoint { 16. 17. /** 18. * This is the entry point method. 19. */ 20. @Override 21. public void onModuleLoad() { 22. Viewport viewport = new Viewport(); 23. final BorderLayout borderLayout = new BorderLayout(); 24. 25. BorderLayoutData northData = new BorderLayoutData(LayoutRegion.NORTH, 26. 20); 27. northData.setCollapsible(false); 28. northData.setSplit(false); 29. 30. HTML headerHtml = new HTML(); 31. headerHtml.setHTML("

RSS Reader

"); 32. viewport.add(headerHtml, northData); 33. 34. BorderLayoutData centerData = new BorderLayoutData(LayoutRegion.CENTER); 35. centerData.setCollapsible(false); 36. 37. BorderLayoutData westData = new BorderLayoutData(LayoutRegion.WEST, 38. 200, 150, 300); 39. westData.setCollapsible(true); 40. westData.setSplit(true); 41. 42. ContentPanel mainPanel = new ContentPanel(); 43. ContentPanel navPanel = new ContentPanel(); 44. viewport.add(mainPanel, centerData); 45. viewport.add(navPanel, westData); 46. 47. viewport.setLayout(borderLayout); 48. RootPanel.get().add(viewport); 49. } 50. } 加载消息 构建任何一种 GUI(graphic user interface 图形用户界面),最重要的一点就是一定要让用 户知道此时此刻应用程序正在做什么。GWT,尤其是 GXT 应用程序,会在 javaScript 和图 片加载时候,占用几秒钟。因此,在这一期间,就需要显示一个等待加载的一个消息界面。 大致思路:当浏览器通过 url 找到此应用程序的时候,也就是 JavaScript 还没有被加载的时 候,我们要在应用程序的 html 页面一直显式一个等待的消息。当浏览器加载完毕 JavaScript 和 UI 被渲染完毕的时候,再把这个等待的消息给隐藏。  打开应用的欢迎 HTML 页面,RSSReader.html。在标记之间,加入 如下的代码: [html] view plaincopyprint? 1.
2.
3. RSS Reader
4. Loading...
5.
标记的 id 必须命名为 loading,当应用加载完毕的时候,就会马上的, 自动的,去隐藏 loading 标记。large-loading.gif 图片,正好是使用 GXT SDK 包里面的 resources 目录里现成的。  接下来,我们要给 loading 标记,指定一个 css 样式。所以我们应该编辑 RSSReader.css 文件,清空里面的内容,然后添加如下的 css 定义: [css] view plaincopyprint? 1. #loading { 2. position: absolute; 3. left: 45%; 4. top: 40%; 5. margin-left: -45px; 6. padding: 2px; 7. z-index: 20001; 8. height: auto; 9. border: 1px solid #ccc; 10. } 11. 12. #loading a { 13. color: #225588; 14. } 15. 16. #loading .loading-indicator { 17. background: white; 18. color: #444; 19. font: bold 13px tahoma, arial, helvetica; 20. padding: 10px; 21. margin: 0; 22. height: auto; 23. } 24. 25. #loading .loading-indicator img { 26. margin-right: 8px; 27. float: left; 28. vertical-align: top; 29. } 30. 31. #loading-msg { 32. font: normal 10px arial, tahoma, sans-serif; 33. }  此时呢,让我们好好通读一下 RSSReader.html。在 JavaScript 加载区域, 毫无疑问的,必须的,要加载 GWT 生成 rssreader/rssreader.nocache.js。 但是通常都是把此类的 JavaScripte 加载的标记片段,放在标记里, 可是这样就意味着他们会早与标记被浏览器先加载进来。换句话说, 就是当 js 文件加载后,找不到标签里面的任何内容。这就无法实现: 在 js 加载后替换 loading 的
标签的想法。因此解决这个问题的好方法就 是,把 js 文件的加载片段,写在标签的最后面。经过修改后的 RSSReader.html 如下: [html] view plaincopyprint? 1. 2. 3. 4. 5. 6. 7. RSSReader 8. 9. 10.
11.
RSS Reader
14. Loading...
15.
16. 18. 19.  如果对于 GWT 的程序而言,想实现 js 加载后隐藏 loading 标记之一功能。 我们要在 onModuleLoad()方法里加入相关的隐藏功能的代码。但是现在我们 是使用 GXT,当我们使用了 Viewport,她就会自动的去寻找 ID 为 loading 的标签,并将他隐藏,不需要我们做任何的操作。事实上,隐藏的过程不是 突然的,而是一个逐渐的过程,这样一来界面就会很友好的显示。如果一个 加载消息的标签,ID 别命名为别的字符串了,我们可以使用 Viewport 的 setLoadingPanelId(java.lang.String loadingPanelId)方法设置一下,照样会达 到效果。  下面运行一下应用,我们会发现,当 UI 被渲染好之后,loading 标记会消失 的。 定制组件 在 GXT,乃至 GWT 中,定制组件是非常普遍并且有用的。定制组件大致可以分为两种: 第一种,修改一个现有组件的功能;第二种,封装一个或多个组件并且加入新的功能,使其 成为一个新的组件。相对于 GXT 而言,GWT 有一个复合组件的概念——通过把一个组件 包装到另外一个组件之上,从而达到定制组件的效果。但是 GXT 如果想实现这种复合组件 会发生很多问题! 比如前面所用到的 ContentPanel,用于 RSSReader 项目的 west 导航区。当把 ContentPanel 生成的对象放入 west 区显示的时候,同时也设置了其可折叠功能。但是如果基于 ContentPanel 创建一个复合组件的话,那个折叠功能的按钮就再也不会出现了。 原因是,当你要创建一个复合组件的话,那些 public API 方法就会不可见。GXT 的设计理 念是,如果要把几个组件复合成一个新的组件的话,就视为你怀疑 GXT components 自带 的功能,既然如此那么这些功能就做废掉了,你将无法使用它们。 综上所述,GXT 制定组件的最佳方法就是继承某个 Component,然后直接或间接的使用其 他的 components。 onRender() 在开始定制组件之前,要再介绍一个非常重要的方法 onRender()。当你继承某个 component 的时候,可选择是否覆盖(override)onRender()方法。这一机制得追溯于先前提到的 GXT 的“懒渲染”概念。当一个 component 在初始化的时候,他的构造函数内的所有代码都会被 执行;类似的,当一个 component 被渲染的时候,onRender()方法内的代码也会被执行。 根据此理论,我们可以很灵活的把需要立即执行的代码写在当前 component 的构造函数理, 把需要在当前 component 被渲染之后,再执行的代码写在 onRender()方法里。按照如此的 编程思想,可以很好的提高 GXT 应用程序的性能和效率。 现在,我们就开始在 RSSReader 项目上,开始第一个定制组件——把原先应用在 west 导 航区和 center 内容区的两个 ContentPanels,替换为自定义的 components。(随着 RSSReader 项目开发,我们将加入更多的定制组件)  在 RSSReader 项目里,新建 package: com.danielvaughan.rssreader.client.components;再在此包下,新建 RssNavigationPanel 类,继承 ContentPanel。作为 west 导航区域显示的内 容。 [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.components; 2. 3. import com.extjs.gxt.ui.client.widget.ContentPanel; 4. public class RssNavigationPanel extends ContentPanel 5. { 6. public RssNavigationPanel() 7. { 8. setHeading("Navigation"); 9. } 10. }  同样的,新建 RssMainPanel 类,作为 center 内容区显示的内容。仅仅设置 一下题头而已。 [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.components; 2. 3. import com.extjs.gxt.ui.client.widget.ContentPanel; 4. 5. public class RssMainPanel extends ContentPanel 6. { 7. public RssMainPanel() 8. { 9. setHeading("Main"); 10. } 11. }  接下来,在 RSSReader 类的 onModuleLoad()方法中,用新建的两个定制 components 替换原来的 ContentPanels [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client; 2. 3. import com.danielvaughan.rssreader.client.components.RssMainPanel; 4. import com.danielvaughan.rssreader.client.components.RssNavigationPanel; 5. import com.extjs.gxt.ui.client.Style.LayoutRegion; 6. import com.extjs.gxt.ui.client.widget.Viewport; 7. import com.extjs.gxt.ui.client.widget.layout.BorderLayout; 8. import com.extjs.gxt.ui.client.widget.layout.BorderLayoutData; 9. import com.google.gwt.core.client.EntryPoint; 10. import com.google.gwt.user.client.ui.HTML; 11. import com.google.gwt.user.client.ui.RootPanel; 12. 13. /** 14. * Entry point classes define onModuleLoad(). 15. */ 16. public class RSSReader implements EntryPoint { 17. 18. /** 19. * This is the entry point method. 20. */ 21. @Override 22. public void onModuleLoad() { 23. Viewport viewport = new Viewport(); 24. final BorderLayout borderLayout = new BorderLayout(); 25. 26. BorderLayoutData northData = new BorderLayoutData(LayoutRegion.NORTH, 27. 20); 28. northData.setCollapsible(false); 29. northData.setSplit(false); 30. 31. HTML headerHtml = new HTML(); 32. headerHtml.setHTML("

RSS Reader

"); 33. viewport.add(headerHtml, northData); 34. 35. BorderLayoutData centerData = new BorderLayoutData(LayoutRegion.CENTER); 36. centerData.setCollapsible(false); 37. 38. BorderLayoutData westData = new BorderLayoutData(LayoutRegion.WEST, 39. 200, 150, 300); 40. westData.setCollapsible(true); 41. westData.setSplit(true); 42. 43. RssMainPanel mainPanel = new RssMainPanel(); 44. RssNavigationPanel navPanel = new RssNavigationPanel(); 45. viewport.add(mainPanel, centerData); 46. viewport.add(navPanel, westData); 47. 48. viewport.setLayout(borderLayout); 49. RootPanel.get().add(viewport); 50. } 51. }  最后运行应用程序,会看到导航区和内容区的题头。 ToggleButton Button 控件对于大家来说并不陌生,也就不专门介绍了。下面要特殊的介绍一下 ToggleButton。他是 Button 的子类,他就像一个开关,有“按下”和“未按下”两种状态。同时 呢,我们可以给 ToggleButton 进行分组,使用 setToggleGroup(String toggleGroup)就可以 实现。分组后的 ToggleButton 不管如何点击其中某个按钮,都会只有一个按钮显示被“按下” 的状态。 添加一个 Link feed button 我们要在 RssNavigationPanel 组件上,添加一个“Link feed” button。其作用是,通过点击 此按钮,显示一个表单,用户可以通过此表单录入一个 RSS 的 URl。当然如果像取消对表 单的录入,通常我们得加入一个 cancel button 来关闭此表单。但是我们现在要使用 ToggleButton 来实现“Link feed” button 的功能——通过两种开关状态,控制表单的显示与 否。 接下来,我们进一步定制 RssNavigationPanel component——添加 ToggleButton  在 RssNavigationPanel 类的构造函数里,新建一个 ToggleButton [java] view plaincopyprint? 1. final ToggleButton btnLinkFeed = new ToggleButton("Link feed");  在 RSSReader.css 文件里,加入合适的 css 样式来设置此 button 的图标。 [css] view plaincopyprint? 1. .link-feed { 2. background: url("gxt/images/icons/feed_link.png") no-repeat center left 3. !important; 4. }  让 btnLinkFeed 应用此 css 样式 [java] view plaincopyprint? 1. btnLinkFeed.setIconStyle("link-feed");  设置 btnLinkFeed 在 RssNavigationPanel 容器里水平居左 [java] view plaincopyprint? 1. setButtonAlign(HorizontalAlignment.LEFT);  最后,要把 btnLinkFeed 添加到 RssNavigationPanel 容器显示出来。 [java] view plaincopyprint? 1. addButton(btnLinkFeed);  运行程序,预期效果如下: Tooltip 自然情况下,我们会让一个 button 的 label 的内容很简洁。一般的用户就会通过 label 明白 此 button 是做什么的。但是还是有些用户无法通过图片和 label 来明白此 button 是用来做 什么的,那么我们就需要使用 Tooltip。 Tooltip可以被添加在一个button或其他的components上。当鼠标经过此component上时, Tooltip 就会显示出来,给用户更多的信息。 接下来,给刚刚添加的 Link feed button 添加一个 Tooltip。  同样还是在 RssNavigationPanel 类的构造函数里,添加如下的代码。 [java] view plaincopyprint? 1. ToolTipConfig linkFeedToolTipConfig = new ToolTipConfig(); 2. linkFeedToolTipConfig.setTitle("Link to existing RSS feed"); 3. linkFeedToolTipConfig 4. .setText("Allows you to enter the URL of an existing RSS feed you would like to link to");  要注意的是 ToolTipConfig 是另外之用 ToolTip——可以支持标题和内容。  设置 btnLinkFeed 的 ToolTip,除了使用 ToolTipConfig 之外,当然还支持 String 类型 [java] view plaincopyprint? 1. btnLinkFeed.setToolTip(linkFeedToolTipConfig);  预期效果如下: ToggleButton Button 控件对于大家来说并不陌生,也就不专门介绍了。下面要特殊的介绍一下 ToggleButton。他是 Button 的子类,他就像一个开关,有“按下”和“未按下”两种状态。同时 呢,我们可以给 ToggleButton 进行分组,使用 setToggleGroup(String toggleGroup)就可以 实现。分组后的 ToggleButton 不管如何点击其中某个按钮,都会只有一个按钮显示被“按下” 的状态〿/P> 添加一个 Link feed button 我们要在 RssNavigationPanel 组件上,添加一个“Link feed‿button。其作用是,通过点击 此按钮,显示一个表单,用户可以通过此表单录入一个 RSS 的 URl。当然如果像取消对表 单的录入,通常我们得加入一个 cancel button 来关闭此表单。但是我们现在要使用 ToggleButton 来实现“Link feed‿button 的功能——通过两种开关状态,控制表单的显示与 否〿BR> 接下来,我们进一步定制 RssNavigationPanel component——添加 ToggleButton  在 RssNavigationPanel 类的构造函数里,新建一个 ToggleButton [java] view plaincopyprint? 1. final ToggleButton btnLinkFeed = new ToggleButton("Link feed");  在 RSSReader.css 文件里,加入合适的 css 样式来设置此 button 的图标〿 /LI> [css] view plaincopyprint? 1. .link-feed { 2. background: url("gxt/images/icons/feed_link.png") no-repeat center left 3. !important; 4. }  让 btnLinkFeed 应用此 css 样式 [java] view plaincopyprint? 1. btnLinkFeed.setIconStyle("link-feed");  设置 btnLinkFeed 在 RssNavigationPanel 容器里水平居巿/LI> [java] view plaincopyprint? 1. setButtonAlign(HorizontalAlignment.LEFT);  最后,要把 btnLinkFeed 添加到 RssNavigationPanel 容器显示出来〿/LI> [java] view plaincopyprint? 1. addButton(btnLinkFeed);  运行程序,预期效果如下: Tooltip 自然情况下,我们会让一个 button 的 label 的内容很简洁。一般的用户就会通过 label 明白 此 button 是做什么的。但是还是有些用户无法通过图片和 label 来明白此 button 是用来做 什么的,那么我们就需要使用 Tooltip〿P> Tooltip可以被添加在一个button或其他的components上。当鼠标经过此component上时, Tooltip 就会显示出来,给用户更多的信息〿/P> 接下来,给刚刚添加的 Link feed button 添加一个 Tooltip〿/P>  同样还是在 RssNavigationPanel 类的构造函数里,添加如下的代码〿BR> [java] view plaincopyprint? 1. ToolTipConfig linkFeedToolTipConfig = new ToolTipConfig(); 2. linkFeedToolTipConfig.setTitle("Link to existing RSS feed"); 3. linkFeedToolTipConfig 4. .setText("Allows you to enter the URL of an existing RSS feed you would like to link to");  要注意的是 ToolTipConfig 是另外之用 ToolTip——可以支持标题和内容〿 /LI>  设置 btnLinkFeed 的 ToolTip,除了使用 ToolTipConfig 之外,当然还支持 String 类型 [java] view plaincopyprint? 1. btnLinkFeed.setToolTip(linkFeedToolTipConfig);  预期效果如下_/LI> Popup Popup 继承自 LayoutContainer,可以使其弹出的窗口,始终至于其他的 components 之上。 现在,我想要在用户点击 Link feed 按钮的时候可以显示一个 Popup 表单窗口,可以让用户 输入一个 URL。  创建新 package:com.danielvaughan.rssreader.client.components。在此包 下创建 LinkFeedPopup 类,继承自 Popup。  然后在构造函数里,加入相关设置,代码如下: [java] view plaincopyprint? 1. public LinkFeedPopup() { 2. setSize(300, 55); 3. setBorders(true); 4. setShadow(true); 5. setAutoHide(false); 6. }  我们设置了其大小,是否有边框,和阴影效果。 SelectionListener 现在我们有 button 和 popup。如果想通过点击 button 可以控制 popup,那么就需要 Listener (正如先前提到的,GXT 对事件的处理是使用的 Listener),首先要把一个 Listener 注册 到某一个 component 上,当有对应的事件发生的时候,就会通知 Listener。因此要在 Listener 里编写相关的代码,以作为此次事件的响应时来执行。在我们的 RSSReader 项目里,Link feed button 的点击 Listener,应该是 SelectionListener(而不是 click 之类的)。具体操作 步骤如下:  因为 Link feed button 被添加到 RssNavigationPanel 里,自然的我们要在 RssNavigationPanel 类,新建一个 LinkFeedPopup。 [java] view plaincopyprint? 1. final LinkFeedPopup linkFeedPopup = new LinkFeedPopup();  要保证 popup 的弹出始终在整个面板 Viewport 内: [java] view plaincopyprint? 1. linkFeedPopup.setConstrainViewport(true);  注册 SelectionListener [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.components; 2. 3. import com.extjs.gxt.ui.client.Style.HorizontalAlignment; 4. import com.extjs.gxt.ui.client.event.ButtonEvent; 5. import com.extjs.gxt.ui.client.event.SelectionListener; 6. import com.extjs.gxt.ui.client.widget.ContentPanel; 7. import com.extjs.gxt.ui.client.widget.button.ToggleButton; 8. import com.extjs.gxt.ui.client.widget.tips.ToolTipConfig; 9. 10. public class RssNavigationPanel extends ContentPanel 11. { 12. public RssNavigationPanel() 13. { 14. setHeading("Navigation"); 15. final ToggleButton btnLinkFeed = new ToggleButton("Link feed"); 16. btnLinkFeed.setIconStyle("link-feed"); 17. setButtonAlign(HorizontalAlignment.LEFT); 18. 19. ToolTipConfig linkFeedToolTipConfig = new ToolTipConfig(); 20. linkFeedToolTipConfig.setTitle("Link to existing RSS feed"); 21. linkFeedToolTipConfig 22. .setText("Allows you to enter the URL of an existing RSS feed you would like to link to"); 23. btnLinkFeed.setToolTip(linkFeedToolTipConfig); 24. 25. final LinkFeedPopup linkFeedPopup = new LinkFeedPopup(); 26. linkFeedPopup.setConstrainViewport(true); 27. btnLinkFeed.addSelectionListener(new SelectionListener() { 28. @Override 29. public void componentSelected(ButtonEvent ce) { 30. if (btnLinkFeed.isPressed()) { 31. linkFeedPopup.show(); 32. } else { 33. linkFeedPopup.hide(); 34. } 35. } 36. }); 37. 38. 39. addButton(btnLinkFeed); 40. } 41. }  给 btnLinkFeed 注册了一个 SelectionListener, 当 btnLinkFeed 被按下的时 候,显示 linkFeedPopup;当 btnLinkFeed 被按起的时候,隐藏 linkFeedPopup TextField TestField 继承自 Field,Field 继承自 BoxComponent。Field 作为表单数据项的基类。这些 具体的 Field,例如 textboxes,checkboxes,listboxes 等,都会被渲染成标准的 HTML 标 签。TextField 则是他们其中之一。一个 GXT 的 TextField 的定义代码如下: [java] view plaincopyprint? 1. TextField text = new TextField(); 其中泛型 String 类型,表示其保存的值的类型。GXT 的 Field 同样具有内建的功能来设置 验证条件。对于我们 RSSReader 项目目前的需求来说,我们需要使用 TextField 来限制用 户只可以输入 URL 的字符串。  因为要在弹出的 popup 窗口构建一个表单,自然我们要在 LinkFeedPopup 类里面编辑。创建一个 TextField 的属性 [java] view plaincopyprint? 1. private final TextField tfUrl = new TextField();  Override LinkFeedPopup 类的onRender()方法,一个重新的,空的onRender() 方法如下: [java] view plaincopyprint? 1. @Override 2. protected void onRender(Element parent, int pos) { 3. super.onRender(parent, pos); 4. }  在 onRender()方法里加入一个 Text component,来告诉用户应该录入 URL: [java] view plaincopyprint? 1. final Text txtExplaination = new Text("Enter a feed url");  创建一个 button,当用户录入 URL 后,可以点击他来提交。 [java] view plaincopyprint? 1. final Button btnAdd = new Button("add"); 2. btnAdd.addSelectionListener(new SelectionListener() { 3. @Override 4. public void componentSelected(ButtonEvent ce) { 5. addFeed(tfUrl.getValue()); 6. } 7. });  在响应点击事件处理的过程里,我们调用的 addFeed 方法,其用来处理具体 的提交之后的操作。目前呢,只是先把用户录入的 URL 弹出一个 Alert 而已。 [java] view plaincopyprint? 1. public void addFeed(String url) { 2. Window.alert("We would now attempt to add " + url + " at this point"); 3. }  之前的操作,大家会发现创建了两个对象,并注册了 Listener。但是并没有 添加到 LinkFeedPopup 类里面设置具体的显示效果。因此我们要对 popup 设置其布局显示效果。 [java] view plaincopyprint? 1. final BorderLayout layout = new BorderLayout(); 2. setLayout(layout);  让 txtExplaination 放在 north 区 [java] view plaincopyprint? 1. final BorderLayoutData northData = new 2. BorderLayoutData(LayoutRegion.NORTH, 20); 3. northData.setMargins(new Margins(2)); 4. add(txtExplaination, northData);  让 textbox 放在 center 区 [java] view plaincopyprint? 1. final BorderLayoutData centerData = new 2. BorderLayoutData(LayoutRegion.CENTER); 3. centerData.setMargins(new Margins(2)); 4. add(tfUrl, centerData);  让提交 button 放在 east 区 [java] view plaincopyprint? 1. final BorderLayoutData eastData = new 2. BorderLayoutData(LayoutRegion.EAST, 50); 3. eastData.setMargins(new Margins(2)); 4. add(btnAdd, eastData);  最后运行一下程序,当点击 Link feed button  录入一个 URL 之后,点击 add button  完整的 LinkFeedPopup 类代码如下 : [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.components; 2. 3. import com.extjs.gxt.ui.client.Style.LayoutRegion; 4. import com.extjs.gxt.ui.client.event.ButtonEvent; 5. import com.extjs.gxt.ui.client.event.ComponentEvent; 6. import com.extjs.gxt.ui.client.event.KeyListener; 7. import com.extjs.gxt.ui.client.event.SelectionListener; 8. import com.extjs.gxt.ui.client.util.Margins; 9. import com.extjs.gxt.ui.client.widget.Popup; 10. import com.extjs.gxt.ui.client.widget.Text; 11. import com.extjs.gxt.ui.client.widget.button.Button; 12. import com.extjs.gxt.ui.client.widget.form.TextField; 13. import com.extjs.gxt.ui.client.widget.layout.BorderLayout; 14. import com.extjs.gxt.ui.client.widget.layout.BorderLayoutData; 15. import com.google.gwt.event.dom.client.KeyCodes; 16. import com.google.gwt.user.client.Element; 17. import com.google.gwt.user.client.Window; 18. 19. public class LinkFeedPopup extends Popup 20. { 21. private final TextField tfUrl = new TextField(); 22. 23. public LinkFeedPopup() { 24. setSize(300, 55); 25. setBorders(true); 26. setShadow(true); 27. setAutoHide(false); 28. } 29. 30. @Override 31. protected void onRender(Element parent, int pos) { 32. super.onRender(parent, pos); 33. final Text txtExplaination = new Text("Enter a feed url"); 34. final Button btnAdd = new Button("add"); 35. btnAdd.addSelectionListener(new SelectionListener() { 36. @Override 37. public void componentSelected(ButtonEvent ce) { 38. addFeed(tfUrl.getValue()); 39. } 40. }); 41. 42. 43. final BorderLayout layout = new BorderLayout(); 44. setLayout(layout); 45. 46. final BorderLayoutData northData = new BorderLayoutData(LayoutRegion.NORTH, 20); 47. northData.setMargins(new Margins(2)); 48. add(txtExplaination, northData); 49. 50. final BorderLayoutData centerData = new BorderLayoutData(LayoutRegion.CENTER); 51. centerData.setMargins(new Margins(2)); 52. add(tfUrl, centerData); 53. 54. final BorderLayoutData eastData = new BorderLayoutData(LayoutRegion.EAST, 50); 55. eastData.setMargins(new Margins(2)); 56. add(btnAdd, eastData); 57. } 58. 59. public void addFeed(String url) { 60. Window.alert("We would now attempt to add " + url + " at this point"); 61. } 62. 63. } Popup 的位置 先前的程序,运行后,会发现 popup 会在整个屏幕的中央显示。这样一来就有些显示的不 友好,最好可以让 Popup 可以在 Link feed button 的上面直接的显示出来。之前用的是 show() 方法,未加入任何的设置,直接显示 popup。为了让让 Popup 可以在 Link feed button 的上 面直接的显示出来,我们就需要在 show()方法上做文章了。在作此之前,先了解一些新的 概念。 1. 一个 button,从根本上说,就是个元素(element)。获得一个 button 的元 素,需要使用 Button.getElement()方法。 2. Popup 类中 public void show(Element elem, String pos)方法的 pos 参数,就 是来规定 Popup 的显示位置的。Popup.show(Button.getElement(), “bl-tl?”) 的意思是说——Popup 的显示位置是以 Button 为基准,Popup 的左下角(bl) 对准 Button 的左上角(tl),来显示的。那么如果 pos 字符串以“?”号为结尾, 就是在“bl-tl”位置定义的基础上,保证 Popup 的显示不会超出整个 Viewport 的范围之外。“-”之前的是 Popup 的基准锚点,“-”之后是 Button.getElement() 的基准锚点。 3. 其他的的 pos 位置定义字符串表格如下: Code Meaning tl The top left corner (default) t The center of the top edge tr The top right corner l The center of the left edge c In the center of the element r The center of the right edge bl The bottom left corner b The center of the bottom edge br The bottom right corner 下面开始修改 popup 的弹出位置  找到 RssNavigationPanel 类,修改 Link feed button 的点击监听事件处理方 法。 [java] view plaincopyprint? 1. btnLinkFeed.addSelectionListener(new SelectionListener() { 2. @Override 3. public void componentSelected(ButtonEvent ce) { 4. if (btnLinkFeed.isPressed()) { 5. linkFeedPopup.show(btnLinkFeed.getElement(), "bl-tl?"); 6. } else { 7. linkFeedPopup.hide(); 8. } 9. } 10. });  运行之后,参照“bl-tl?” 字符串的位置设置,理解一下 linkFeedPopup.show(btnLinkFeed.getElement(), "bl-tl?");到底是说明了什么 意思。 KeyListener 接下来进一步完善我们的 RSSReader 项目:参看上面的截图,当用户输入 URL 之后,通 过点击 add button 完成整个操作。但是习惯上来说,用户输入 URL 之后会直接敲击一下回 车键,来表明完成了整个操作。这样的操作过程也很快捷方便,不需要用户就动鼠标去点击 button。 TextField component 支持监听(KeyListener)键盘操作的事件。回车键的 key code 早已 经在 GWT 里有了定义——KeyCodes.KEY_ENTER。因此我们要在 TextField 注册一个 KeyListener,当 TextField 里有回车键传入的时候,表明 URL 已经录入完毕。 如下是加入 KeyListener 的完整代码: [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.components; 2. 3. import com.extjs.gxt.ui.client.Style.LayoutRegion; 4. import com.extjs.gxt.ui.client.event.ButtonEvent; 5. import com.extjs.gxt.ui.client.event.ComponentEvent; 6. import com.extjs.gxt.ui.client.event.KeyListener; 7. import com.extjs.gxt.ui.client.event.SelectionListener; 8. import com.extjs.gxt.ui.client.util.Margins; 9. import com.extjs.gxt.ui.client.widget.Popup; 10. import com.extjs.gxt.ui.client.widget.Text; 11. import com.extjs.gxt.ui.client.widget.button.Button; 12. import com.extjs.gxt.ui.client.widget.form.TextField; 13. import com.extjs.gxt.ui.client.widget.layout.BorderLayout; 14. import com.extjs.gxt.ui.client.widget.layout.BorderLayoutData; 15. import com.google.gwt.event.dom.client.KeyCodes; 16. import com.google.gwt.user.client.Element; 17. import com.google.gwt.user.client.Window; 18. 19. public class LinkFeedPopup extends Popup { 20. private final TextField tfUrl = new TextField(); 21. 22. public LinkFeedPopup() { 23. setSize(300, 55); 24. setBorders(true); 25. setShadow(true); 26. setAutoHide(false); 27. } 28. 29. @Override 30. protected void onRender(Element parent, int pos) { 31. super.onRender(parent, pos); 32. final Text txtExplaination = new Text("Enter a feed url"); 33. final Button btnAdd = new Button("add"); 34. btnAdd.addSelectionListener(new SelectionListener() { 35. @Override 36. public void componentSelected(ButtonEvent ce) { 37. addFeed(tfUrl.getValue()); 38. } 39. }); 40. 41. tfUrl.addKeyListener(new KeyListener() { 42. @Override 43. public void componentKeyDown(ComponentEvent event) { 44. if (event.getKeyCode() == KeyCodes.KEY_ENTER) { 45. addFeed(tfUrl.getValue()); 46. } 47. } 48. }); 49. 50. final BorderLayout layout = new BorderLayout(); 51. setLayout(layout); 52. 53. final BorderLayoutData northData = new BorderLayoutData( 54. LayoutRegion.NORTH, 20); 55. northData.setMargins(new Margins(2)); 56. add(txtExplaination, northData); 57. 58. final BorderLayoutData centerData = new BorderLayoutData( 59. LayoutRegion.CENTER); 60. centerData.setMargins(new Margins(2)); 61. add(tfUrl, centerData); 62. 63. final BorderLayoutData eastData = new BorderLayoutData( 64. LayoutRegion.EAST, 50); 65. eastData.setMargins(new Margins(2)); 66. add(btnAdd, eastData); 67. } 68. 69. public void addFeed(String url) { 70. Window.alert("We would now attempt to add " + url + " at this point"); 71. } 72. 73. } 第三章:表单和窗口 本章我们要学习 GXT 的表单功能。了解相关的表单 components 和如何使用他们。也会涉 及到 GXT Registry,来学习如何跨越各个 components 来传递对象的。 本章,我们会涉及到如下 GXt 功能集  The full range of fields available in GXT  FormPanel  FormLayout  Window  FitLayout  FieldMessages  Form submission  Working with GWT RPC  Using the registry RSSReader 项目新增需求 目前为止, 我们的 RSSReader 虚拟项目已经有一个大致的雏形了。但对于经常发散思维 的用户来说,如此的一个小 RSSReader 项目,功能上是无法满足用户需求的。 比如用户就说了 :现在不仅仅可以通过 URL 获得 RSS 信息,还可以自己创建他们。这 就意味着我们需要创建一个表单,给用户提供录入的页面。对于 GXT 来说,这点需求不在 话下。 RSS2.0 规范 RSSReader 项目可以按照 RSS2.0 的规范读取 RSS 的 URL,来获得数据。一个简单的 RSS feed 其实就是个 XML 格式的标签包裹着指定的内容。因此如果想创建一个 RSS feed 就得 按照其规范创建。 RSS2.0 的详细说明:http://cyber.law.harvard.edu/rss/rss.html。例子: http://cyber.law.harvard.edu/rss/examples/rss2sample.xml 简单来说,一个 RSS 的 xml 有 channel 和多个 RSS feed 的 item 组成的。RSS feed 的 item 大致是由:title,link,description 三个元素组成,所以就要创建与之对应的两个表单(Form), 一个用来接收 channel 信息,一个用来接收 item 信息。 FormPanel FormPanel 继承自 ContentPanel,他可以管理有关表单的 components。默认情况下, FormPanel 的布局效果是设置成 FormLayout。FormPanel 只允许其添加 Field 的 components,例如:TextField,LabelField 等。如果添加了其他的 components 是不会被 渲染的,会被自动忽略。 FormPanel 的最大用户在于,可以作为其添加的 Fields 的总管。可以通过设置 FormPanel 来影响其包含的所有的 Fields components。比如设置只读属性,验证表单,Fields 的显示 状态,最后还包括表单的提交功能——HTTP POST 提交和 GWT RPC(稍后提到)。 Fields 对于具体的 Fields 的介绍,说实话 GXT 我也是探索中曲折前进。大家可以参看: http://www.sencha.com/examples/explorer.html#forms 创建一个 feed button 既然用户提出需求了,我们就要开始开发新的功能了。我们需要在 RssNavigationPanel 类 中,添加一个 button,来允许用户添加一个具体的 feed 信息。此次是实用的的 Button,而 不是 ToggleButton。  编辑 RSSReader.css:加入如下的 css 样式,为了给“Create feed”button 应 用此样式。 [css] view plaincopyprint? 1. .create-feed { 2. background: url(gxt/images/icons/feed_create.png) no-repeat center left 3. !important; 4. }  RssNavigationPanel 类构造函数中,继续加入“Create feed”button 的定义。 [java] view plaincopyprint? 1.
 final Button btnCreateFeed = new   Button("Create feed");    2. btnCreateFeed.setIconStyle("create-feed");    3. ToolTipConfig createNewToolTipConfig = new ToolTipConfig();    4. createNewToolTipConfig.setTitle("Create a new RSS feed");    5. createNewToolTipConfig.setText("Creates a new RSS feed");    6. btnCreateFeed.setToolTip(createNewToolTipConfig);    7. addButton(btnCreateFeed);    8.    9. addButton(btnLinkFeed);
10.
    11. 
    12.
  • 运行程序,观看“Create feed”button 的显示效果
13. 14.

15.

16.

创建一个 Feed 类

17. 创建一个用来存储 feed 信息的 POJO 类。之后他会通过 Google RPC 发送,所以要保证 Feed 类 implements Serializable 接口,并且有无参数的构造函数。 18.

19.

Feed 类既被 client 端使用,又会被 Server 端使用。因此,相对于 client 和 server 包,最好把他放在 shared 包下更合适。那么 shared 包里的类,就会被 client 和 server 类共同调用。

20.

在这里要重点说明一下,GWT 不是会把项目里面所有的 package 都作为编译成 javaScript 的 source 代码,而是通过 module xml 文件配置的(RSSReader.gwt.xml)。 因此需要把 shared 包加入 GWT 的编译路径中去。

21.
    22.
  • 创建新包:com.danielvaughan.rssreader.shared.model
  • 修改 RSSReader.gwt.xml,加入另外一个 GWT 编译入口——shared package:
23.
     24.     25. 
26.
    27.
  • 如上配置,client 就是指:com.danielvaughan.rssreader.client,shared 就 是指:com.danielvaughan.rssreader.shared,为什么 GWT 就就此翻译成此路径,我 个人认为是 RSSReader.gwt.xml配置文件存放在com.danielvaughan.rssreader包下 的原因。
    28.
29.
    30.
  • 创建 Feed 类,放在 com.danielvaughan.rssreader.shared.model 下:
31.
package   com.danielvaughan.rssreader.shared.model;    32.    33. import java.io.Serializable;    34.    35. @SuppressWarnings("serial")    36. public class Feed implements Serializable {    37.    38. private String description;    39. private String link;    40. private String title;    41. private String uuid;    42.    43. public Feed() {    44.    45. }    46.    47. public Feed(String uuid) {    48. this.uuid = uuid;    49. }    50.    51. public String getDescription() {    52. return description;    53. }    54.    55. public String getLink() {    56. return link;    57. }    58.    59. public String getTitle() {    60. return title;    61. }    62.    63. public String getUuid() {    64. return uuid;    65. }    66.    67. public void setDescription(String description) {    68. this.description = description;    69. }    70.    71. public void setLink(String link) {    72. this.link = link;    73. }    74.    75. public void setTitle(String title) {    76. this.title = title;    77. }    78. }    79. 
80.
    81.
  • 目前为止,整个项目 RSSReader 的项目结构如下:
82.
83.
84.


85.

86.


87.

88.
    Window   Window 的父类的 ContentPanel,他是在程序中用来显示窗口的 component。有些类似与  Popup,但是 Window 的窗口有已经定义好的布局效果,比如拖拽,关闭按钮,用户拖拉其  大小等。         FitLayout   如果我们要在 Window 里加入一个表单,我们希望整个表单可以填满整个 Widnow 窗口,  所以就要使用 FitLayout 布局。任何一个包裹着一个子集的容器,都可以应用此布局。   创建 FeedWindow component   接下来,我们要创建一个新的 Widnow,来作为 feed 表单的容器。通过 Create feet button  来显示此 Window。    创建新包:com.danielvaughan.rssreader.client.windows,在此包下,创建  新 FeedWindow 类   [java] view plaincopyprint?   1. public class FeedWindow extends Window {}     同样要在构造函数里,设置题头。但是此次的构造函数,有一个参数是 Feed   [java] view plaincopyprint?   1. public FeedWindow(final Feed feed) {    2. setHeading("Feed");    3. }     再加入一些其他的设置   [java] view plaincopyprint?   1. public FeedWindow(final Feed feed) {    2. setHeading("Feed");    3. setWidth(350);    4. setHeight(200);    5. setResizable(false);    6. setLayout(new FitLayout());    7. }     Create feed Button 先前被添加到 RssNavigationPanel 类中。现在,我们需  要给 Create feed button,添加 SelectionListener,来新建 FeedWindow 窗  口。   [java] view plaincopyprint?   1. btnCreateFeed    2. .addSelectionListener(new SelectionListener() {    3. @Override    4. public void componentSelected(ButtonEvent ce) {    5. createNewFeedWindow();    6. }    7. });     在 RssNavigationPanel 类中,加入 createNewFeedWindow()方法的实现。   [java] view plaincopyprint?   1. private void createNewFeedWindow() {    2. final Window newFeedWindow = new FeedWindow(new Feed());    3. newFeedWindow.show();    4. }        运行程序,当我们点击 Create feed button 的时候,就会弹出 FeedWindow。             最后,完整的 RssNavigationPanel 类如下:   [java] view plaincopyprint?   1. package com.danielvaughan.rssreader.client.components;    2.    3. import com.danielvaughan.rssreader.client.windows.FeedWindow;    4. import com.danielvaughan.rssreader.shared.model.Feed;    5. import com.extjs.gxt.ui.client.Style.HorizontalAlignment;    6. import com.extjs.gxt.ui.client.event.ButtonEvent;    7. import com.extjs.gxt.ui.client.event.SelectionListener;    8. import com.extjs.gxt.ui.client.widget.ContentPanel;    9. import com.extjs.gxt.ui.client.widget.Window;    10. import com.extjs.gxt.ui.client.widget.button.Button;    11. import com.extjs.gxt.ui.client.widget.button.ToggleButton;    12. import com.extjs.gxt.ui.client.widget.tips.ToolTipConfig;    13.    14. public class RssNavigationPanel extends ContentPanel {    15. public RssNavigationPanel() {    16. setHeading("Navigation");    17. final ToggleButton btnLinkFeed = new ToggleButton("Link feed");    18. btnLinkFeed.setIconStyle("link-feed");    19. setButtonAlign(HorizontalAlignment.LEFT);    20.    21. ToolTipConfig linkFeedToolTipConfig = new ToolTipConfig();    22. linkFeedToolTipConfig.setTitle("Link to existing RSS feed");    23. linkFeedToolTipConfig    24. .setText("Allows you to enter the URL of an existing RSS feed you would   like to link to");    25. btnLinkFeed.setToolTip(linkFeedToolTipConfig);    26.    27. final LinkFeedPopup linkFeedPopup = new LinkFeedPopup();    28. linkFeedPopup.setConstrainViewport(true);    29. btnLinkFeed.addSelectionListener(new SelectionListener() {    30. @Override    31. public void componentSelected(ButtonEvent ce) {    32. if (btnLinkFeed.isPressed()) {    33. linkFeedPopup.show(btnLinkFeed.getElement(), "bl-tl?");    34. } else {    35. linkFeedPopup.hide();    36. }    37. }    38. });    39.    40. final Button btnCreateFeed = new Button("Create feed");    41. btnCreateFeed.setIconStyle("create-feed");    42. ToolTipConfig createNewToolTipConfig = new ToolTipConfig();    43. createNewToolTipConfig.setTitle("Create a new RSS feed");    44. createNewToolTipConfig.setText("Creates a new RSS feed");    45. btnCreateFeed.setToolTip(createNewToolTipConfig);    46.    47. btnCreateFeed    48. .addSelectionListener(new SelectionListener() {    49. @Override    50. public void componentSelected(ButtonEvent ce) {    51. createNewFeedWindow();    52. }    53. });    54.    55. addButton(btnCreateFeed);    56.    57. addButton(btnLinkFeed);    58. }    59.    60. private void createNewFeedWindow() {    61. final Window newFeedWindow = new FeedWindow(new Feed());    62. newFeedWindow.show();    63. }    64. }    新建 FeedForm   先前窗口的工作已经完成了,下面要在窗口里加入表单。    新建包:com.danielvaughan.rssreader.client.forms,在此包下新建  FeedForm 类,继承 FormPanel。    通过构造函数,设置隐藏题头   [java] view plaincopyprint?   1. public FeedForm()    2. {    3. setHeaderVisible(false);    4. }     定义表单中的 Fields,就是先前提到的 title,link,description   [java] view plaincopyprint?   1. private final TextField tfTitle = new TextField();    2. private final TextArea taDescription = new TextArea();    3. private final TextField tfLink = new TextField();        我们希望这三个 Fields,在当前的 FeedForm 被渲染之后,才被渲染。因此  override onRender()方法   [java] view plaincopyprint?   1. @Override    2. protected void onRender(Element parent, int pos) {    3. super.onRender(parent, pos);    4. tfTitle.setFieldLabel("Title");    5. taDescription.setFieldLabel("Description");    6. tfLink.setFieldLabel("Link");    7. add(tfTitle);    8. add(taDescription);    9. add(tfLink);    10. }     FeedForm 类编写完毕之后,要把它添加到先前的 FeedWindow 类中。因此  打开 FeedWindow 类,加入属性 FeedForm   [java] view plaincopyprint?   1. private final FeedForm feedForm = new FeedForm();     同样,在 onRender()方法中渲染 feedForm   [java] view plaincopyprint?   1. @Override    2. protected void onRender(Element parent, int pos) {    3. super.onRender(parent, pos);    4. add(feedForm);    5. }        此时运行程序,再次点击 Create feed button 的时候,弹出的 FeedWindow  里就会显示 FeedForm         Validation Fields   GXT Fields 提供内建的校验功能。通过 isValid()方法,可以验证任何的 Field 是否包含有效  值。当 Fields 被包含在 FormPanel 中,那么就可以调用 FormPanel.isValid()来验证其所有  的子 Fields 是否都有效。   默认情况下,Fields 的校验时间是在用户编辑完毕(on blur)。当然我们可以通过  setAutoValidate(true),让用户的每次录入都会校验(key press)。    接下来,对 FeedForm 的表单 Fields 进行校验。打开 FeedForm class,编辑  onRender(),加入校验的设置代码:   [java] view plaincopyprint?   1. @Override    2. protected void onRender(Element parent, int pos) {    3. super.onRender(parent, pos);    4. tfTitle.setFieldLabel("Title");    5. tfTitle.setAllowBlank(false);    6. taDescription.setFieldLabel("Description");    7. taDescription.setAllowBlank(false);    8. tfLink.setFieldLabel("Link");    9. tfLink.setAllowBlank(false);    10. tfLink.setRegex("^http\\://[a-zA-Z0-9\\-\\.]+\\.[a-zAZ]{    11. 2,3}(/\\S*)?{1}quot;);    12. }        运行效果如下:         设置验证提示消息   就像设置校验策略一样,同样可以设置不同验证策略下的提示内容——FieldMessages   Fields 已经使用内部类实现了 FieldMessages。所以设置消息的最好方式是使用 Field 里的  getMessages()获得 FieldMessages 之后,在设置合适的消息内容。    具体代码如下。   [java] view plaincopyprint?   1. @Override    2. protected void onRender(Element parent, int pos) {    3. super.onRender(parent, pos);    4.    5. tfTitle.setFieldLabel("Title");    6. tfTitle.setAllowBlank(false);    7. tfTitle.getMessages().setBlankText("Title is required");    8.    9. taDescription.setFieldLabel("Description");    10. taDescription.setAllowBlank(false);    11. taDescription.getMessages().setBlankText("Description is required");    12.    13. tfLink.setFieldLabel("Link");    14. tfLink.setAllowBlank(false);    15. tfLink.setRegex("^http\\://[a-zA-Z0-9\\-\\.]+\\.[a-zA-Z]{2,3}(/\\S*)?{  1}quot;);    16. tfLink.getMessages().setBlankText("Link is required");    17. tfLink.getMessages()    18. .setRegexText(    19. "The link field must be a URL e.g. http://www.example.com/rss.xml");    20.    21. add(tfTitle);    22. add(taDescription);    23. add(tfLink);    24. }        显示效果:          完整的 FeedForm 类如下:   [java] view plaincopyprint?   1. package com.danielvaughan.rssreader.client.forms;    2.    3. import com.danielvaughan.rssreader.client.RSSReaderConstants;    4. import com.danielvaughan.rssreader.client.services.FeedServiceAsync;    5. import com.danielvaughan.rssreader.shared.model.Feed;    6. import com.extjs.gxt.ui.client.Registry;    7. import com.extjs.gxt.ui.client.widget.Info;    8. import com.extjs.gxt.ui.client.widget.form.FormPanel;    9. import com.extjs.gxt.ui.client.widget.form.TextArea;    10. import com.extjs.gxt.ui.client.widget.form.TextField;    11. import com.google.gwt.user.client.Element;    12. import com.google.gwt.user.client.rpc.AsyncCallback;    13.    14. public class FeedForm extends FormPanel {    15.    16. private final TextField tfTitle = new TextField();    17. private final TextArea taDescription = new TextArea();    18. private final TextField tfLink = new TextField();    19.    20. public FeedForm() {    21. setHeaderVisible(false);    22. }    23.    24. @Override    25. protected void onRender(Element parent, int pos) {    26. super.onRender(parent, pos);    27.    28. tfTitle.setFieldLabel("Title");    29. tfTitle.setAllowBlank(false);    30. tfTitle.getMessages().setBlankText("Title is required");    31.    32. taDescription.setFieldLabel("Description");    33. taDescription.setAllowBlank(false);    34. taDescription.getMessages().setBlankText("Description is required");    35.    36. tfLink.setFieldLabel("Link");    37. tfLink.setAllowBlank(false);    38. tfLink.setRegex("^http\\://[a-zA-Z0-9\\-\\.]+\\.[a-zA-Z]{2,3}(/\\S*)?{  1}quot;);    39. tfLink.getMessages().setBlankText("Link is required");    40. tfLink.getMessages()    41. .setRegexText(    42. "The link field must be a URL e.g. http://www.example.com/rss.xml");    43.    44. add(tfTitle);    45. add(taDescription);    46. add(tfLink);    47. }    48. }    表单使用 HTTP 提交   表单有两种提交方式,第一种就是传统的 HTTP 提交。   最直接的步骤就是:    使用 FormPanel 的 setAction()方法,去定义 submit 的 URL    使用 FormPanel 的 isValid()方法,去验证所有的 fields 是否有正确    如果正确,使用 FormPanel 的 submit()方法提交表单。   [java] view plaincopyprint?   1. setAction("http://www.example.com/submit.php");    2. final Button btnSave = new Button("Save");    3. btnSave.setIconStyle("save");    4. btnSave.addSelectionListener(new SelectionListener()    5. {    6. public void componentSelected(ButtonEvent ce) {    7. if (isValid())    8. {    9. submit();    10. } }    11. });    12. addButton(btnSave);       GWT RPC   表单就拥有了另外一种提交方式——RPC。对于 GWT 和 GXT 来说,他不像传统的 web 应  用,在 client 端我们可以选择存储和操作 java 类型的数据——不管这些数据是在 web 前端  还是通过 GWT RPC 或者 JSON 别的什么方法提交到后端,我们都可以操作他们。   在 RSSReader 项目里,我们就会使用 GWT RPC。   创建一个 Feed service   为了能够检索到 Feed 对象,就必须有唯一的 ID。java 里有专门用来生成的主键的类  ——UUID。接下来我们要实现一个功能,就是当在 client 端所使用 Feed 的实例对象,是  通过 RPC 从 server 端返回的,并且已经赋值 ID 的。因为先前,我们所创建的 Feed 类,  就是为了在 client 和 server 两端都可以使用。那么接下来就要创建一个 GWT RPC的 service  去处理 Feed 实例对象。    创建新包:com.danielvaughan.rssreader.client.services,在此包下,新建一  个接口 FeedService,继承 GWT RemoteService。定义一个方法  createNewFeed():   [java] view plaincopyprint?   1. package com.danielvaughan.rssreader.client.services;    2.    3. import com.danielvaughan.rssreader.shared.model.Feed;    4. import com.google.gwt.user.client.rpc.RemoteService;    5. import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;    6.    7. @RemoteServiceRelativePath("feed-service")    8. public interface FeedService extends RemoteService {    9. Feed createNewFeed();    10. }     注意 1:@RemoteServiceRelativePath("feed-service") 注解了当前这个  service,在 web.xml 应该配置的 url 映射地址。也就是说不管哪个类实现了  FeedService 接口,那么他在 web.xml 的 servlet url 映射就应该设置成  "feed-service"。    注意 2:FeedService 接口创建完毕之后,GWT 就会自动的提示你去创建与  之对应的另外一个接口 FeedServiceAsync      [java] view plaincopyprint?   1. package com.danielvaughan.rssreader.client.services;    2.    3. import com.danielvaughan.rssreader.shared.model.Feed;    4. import com.google.gwt.user.client.rpc.AsyncCallback;    5.    6. public interface FeedServiceAsync {    7.    8. void createNewFeed(AsyncCallback callback);    9.    10. }     FeedServiceAsync 接口中生成了 FeedService 接口中的,异步回调的抽象方  法。其目的是在 createNewFeed()方法执行之后的返回期间会调用  FeedServiceAsync 接口中的与之对应的  createNewFeed(AsyncCallback callback)方法,来作为  createNewFeed()方法执行情况的反馈。    之前的两个接口都是属于 client 端的 services,具体的实现类就应该属于的  server 端的 services。所以新建包:  com.danielvaughan.rssreader.server.services,在此包下创建  FeedServiceImpl 类实现 FeedService,继承 RemoteServiceServlet。具体  的实现如下代码:   [java] view plaincopyprint?   1. package com.danielvaughan.rssreader.server.services;    2.    3. import java.io.IOException;    4. import java.util.UUID;    5.    6. import com.danielvaughan.rssreader.client.services.FeedService;    7. import com.danielvaughan.rssreader.shared.model.Feed;    8. import com.google.gwt.user.server.rpc.RemoteServiceServlet;    9.    10. @SuppressWarnings("serial")    11. public class FeedServiceImpl extends RemoteServiceServlet implements    12. FeedService {    13. @Override    14. public Feed createNewFeed() {    15. UUID uuid = UUID.randomUUID();    16. return new Feed(uuid.toString());    17. }    18.    19. }        配置 FeedServiceImpl 的 servlet 映射。编辑 web.xml   [html] view plaincopyprint?   1.     2. feedServlet    3.     4. com.danielvaughan.rssreader.server.services.FeedServiceImpl    5.     6.     7.     8. feedServlet    9. /rssreader/feed-service    10.         注意:/rssreader/feed-service其中的 rssreader  是小写,这与是一直的。    RPC的services已经创建完毕了。只要记住他包括client端和server端代码。  client 端是接口的定义,server 端是具体的实现。web.xml 里配置的 server  端的 servlet。            Registry   GXT 提供一个 Registry 类。他就像 HashMap,可以在应用程序的 client 端内随意使用它。  他就像一个载体,穿梭在 client 端的任何类之间,用于传递数据。      Registry 存储 services   Registry 就是用来存储数据的。数据可以是多种多样的,除了基本的 list 之外,他可以存储  services——当 services 被创建的时候,就被放入 Registry 内,然后可以在 client 端的任何  位置,调用 registry,取出 services,通过 services 调用 server 端的代码,大致套路就是这  样了 。对于 RSSReader 项目目前而言,registry 要存放的 service 是 FeedServiceAsync,  当从 registry 中取出 FeedServiceAsync(services) ,去调用 FeedServiceAsync 里的方法,  那么就会调用 server 端的 FeedServiceImpl 类中对应的具体实现方法。    首先,要创建一个 const 类,用来定义放入 Registry 中的  keys——RSSReaderConstants   [java] view plaincopyprint?   1. package com.danielvaughan.rssreader.client;    2.    3. public class RSSReaderConstants {    4. public static final String FEED_SERVICE = "feedService";    5. }     要在程序最开始的时候,就把 services 注册到 Registry 内。所以编辑  RSSReader 类的 onModuleLoad()方法,加入如下代码:   [java] view plaincopyprint?   1. Registry.register(RSSReaderConstants.FEED_SERVICE,    2. GWT.create(FeedService.class));     注意:GWT.create(FeedService.class)的含义是,通过 FeedService.class,  得到了 FeedServiceAsync.class。    修改 RssNavigationPanel 类的 createNewFeedWindow(),加入从 Registry  内获得 FeedServiceAsync 类的代码:   [java] view plaincopyprint?   1. private void createNewFeedWindow()    2. {    3. final FeedServiceAsync feedService =    4. Registry.get(RSSReaderConstants.FEED_SERVICE);    5. }     得到了 FeedServiceAsync,下面就要通过 GWT 的 RPC 去调用 server 端了  (FeedServiceImpl)。完整的在 createNewFeedWindow()方法内,调用 RPC  的代码如下:   [java] view plaincopyprint?   1. private void createNewFeedWindow() {    2. final FeedServiceAsync feedService = Registry    3. .get(RSSReaderConstants.FEED_SERVICE);    4. feedService.createNewFeed(new AsyncCallback() {    5. @Override    6. public void onFailure(Throwable caught) {    7. Info.display("RSSReader", "Unable to create a new feed");    8. }    9.    10. @Override    11. public void onSuccess(Feed feed) {    12. final Window newFeedWindow = new FeedWindow(feed);    13. newFeedWindow.show();    14. }    15. });    16. }        注意,上面代码的执行流程是:首先调用 FeedServiceImpl.createNewFeed()  方法。当执行完毕之后,成功了就会回调 onSuccess(),反之失败了,就会  回调 onFailure()方法。注意此过程是异步的(asynchronous)。    因此整个过程实现了,当点击 create feed button 的时候,会调用  createNewFeedWindow()方法,去请求一个有 ID 主键的 Feed 对象。如果  Feed 对象成功返回之后,新建一个 FeedWindow 窗口,同时传入刚刚成功  返回的 feed,然后显示 FeedWindow 窗口。Registry 在此过程中,提供了调  用 RPC 的 feedService。      保存 Feed   接下来,在 FeedForm 类中要新建 save()方法,用来接收用户录入的信息,存储到先前通  过 RPC 成功返回的 feed 中去。当然还得添加一个 button 用来验证表单,当验证通过之后,  调用 save()方法。    在 FeedForm 类中要新建 save(),用来接收用户录入的信息:   [java] view plaincopyprint?   1. public void save(final Feed feed) {    2. feed.setTitle(tfTitle.getValue());    3. feed.setDescription(taDescription.getValue());    4. feed.setLink(tfLink.getValue());    5.    6. }     在 FeedWindow 类中加入 Save button。当 form 验证通过之后,调用  FeedForm 类中 save()方法。   [java] view plaincopyprint?   1. package com.danielvaughan.rssreader.client.windows;    2.    3. import com.danielvaughan.rssreader.client.forms.FeedForm;    4. import com.danielvaughan.rssreader.shared.model.Feed;    5. import com.extjs.gxt.ui.client.event.ButtonEvent;    6. import com.extjs.gxt.ui.client.event.SelectionListener;    7. import com.extjs.gxt.ui.client.widget.Window;    8. import com.extjs.gxt.ui.client.widget.button.Button;    9. import com.extjs.gxt.ui.client.widget.layout.FitLayout;    10. import com.google.gwt.user.client.Element;    11.    12. public class FeedWindow extends Window {    13.    14. private final FeedForm feedForm = new FeedForm();    15.    16. public FeedWindow(final Feed feed) {    17. setHeading("Feed");    18. setWidth(350);    19. setHeight(200);    20. setResizable(false);    21. setLayout(new FitLayout());    22.    23. final Button btnSave = new Button("Save");    24. btnSave.setIconStyle("save");    25. btnSave.addSelectionListener(new SelectionListener() {    26. public void componentSelected(ButtonEvent ce) {    27. btnSave.setEnabled(false);    28. if (feedForm.isValid()) {    29. hide(btnSave);    30. feedForm.save(feed);    31. } else {    32. btnSave.setEnabled(true);    33. }    34. }    35. });    36. addButton(btnSave);    37. }    38.    39. @Override    40. protected void onRender(Element parent, int pos) {    41. super.onRender(parent, pos);    42. add(feedForm);    43. }    44.    45. }        当然要给 Save button 美化一下。   [css] view plaincopyprint?   1. .save {    2. background: url(gxt/images/icons/disk.png) no-repeat center    3. left    4. !important;    5. }        运行之后的效果:          当点击 save button 之后,用户录入的信息,就会存入到 feed 对象中。填充  好 feed 之后的持久化操作,下一节会提到。   创建 RSS XML   接下来,要把用户录入的 feed 内容保存起来。最合适的方式是,保存为 RSS XML 格式的  文档。所以要在 FeedService 接口中新增一个方法 saveFeed()方法。   我们需要使用 JDOM 1.1 库,操作 feed 对象,将其转换成 XML 格式。  http://www.jdom.org/downloads/index.html 下载 jdom-1.1.2.jar 包,放入工作空间里。    编辑 FeedService 接口,加入:   [java] view plaincopyprint?   1. void saveFeed(Feed feed);        GWT 在 Eclipse 中会提示并自动在 FeedServiceAsync 接口里生成:   [java] view plaincopyprint?   1. void saveFeed(Feed feed, AsyncCallback callback);        在 FeedServiceImpl 类中,实现 saveFeed 方法。   [java] view plaincopyprint?   1. @Override    2. public void saveFeed(Feed feed) {    3. Element eleRoot = new Element("rss");    4. eleRoot.setAttribute(new Attribute("version", "2.0"));    5.    6. // Create a document from the feed object    7. Document document = new Document(eleRoot);    8.    9. Element eleChannel = new Element("channel");    10. Element eleTitle = new Element("title");    11. Element eleDescription = new Element("description");    12. Element eleLink = new Element("link");    13.    14. eleTitle.setText(feed.getTitle());    15. eleDescription.setText(feed.getDescription());    16. eleLink.setText(feed.getLink());    17.    18. eleChannel.addContent(eleTitle);    19. eleChannel.addContent(eleDescription);    20. eleChannel.addContent(eleLink);    21.    22. eleRoot.addContent(eleChannel);    23.    24. try {    25. XMLOutputter serializer = new XMLOutputter();    26. Format prettyFormat = Format.getPrettyFormat();    27. serializer.setFormat(prettyFormat);    28. System.out    29. .println("At this point we would serialize the feed "    30. + feed.getTitle()    31. + " to a file. For now we are just going to write it to the console.");    32. serializer.output(document, System.out);    33. } catch (IOException e) {    34. System.out.println("Error saving feed");    35. }    36. }     上面的代码,只是把 feed 数据,转换成 document 。然后将 document 输出  到控制台。    编辑 FeedForm 类的 save 方法。加入 RPC 的调用代码。   [java] view plaincopyprint?   1. package com.danielvaughan.rssreader.client.forms;    2.    3. import com.danielvaughan.rssreader.client.RSSReaderConstants;    4. import com.danielvaughan.rssreader.client.services.FeedServiceAsync;    5. import com.danielvaughan.rssreader.shared.model.Feed;    6. import com.extjs.gxt.ui.client.Registry;    7. import com.extjs.gxt.ui.client.widget.Info;    8. import com.extjs.gxt.ui.client.widget.form.FormPanel;    9. import com.extjs.gxt.ui.client.widget.form.TextArea;    10. import com.extjs.gxt.ui.client.widget.form.TextField;    11. import com.google.gwt.user.client.Element;    12. import com.google.gwt.user.client.rpc.AsyncCallback;    13.    14. public class FeedForm extends FormPanel {    15.    16. private final TextField tfTitle = new TextField();    17. private final TextArea taDescription = new TextArea();    18. private final TextField tfLink = new TextField();    19.    20. public FeedForm() {    21. setHeaderVisible(false);    22. }    23.    24. @Override    25. protected void onRender(Element parent, int pos) {    26. super.onRender(parent, pos);    27.    28. tfTitle.setFieldLabel("Title");    29. tfTitle.setAllowBlank(false);    30. tfTitle.getMessages().setBlankText("Title is required");    31.    32. taDescription.setFieldLabel("Description");    33. taDescription.setAllowBlank(false);    34. taDescription.getMessages().setBlankText("Description is required");    35.    36. tfLink.setFieldLabel("Link");    37. tfLink.setAllowBlank(false);    38. tfLink.setRegex("^http\\://[a-zA-Z0-9\\-\\.]+\\.[a-zA-Z]{2,3}(/\\S*)?{  1}quot;);    39. tfLink.getMessages().setBlankText("Link is required");    40. tfLink.getMessages()    41. .setRegexText(    42. "The link field must be a URL e.g. http://www.example.com/rss.xml");    43.    44. add(tfTitle);    45. add(taDescription);    46. add(tfLink);    47. }    48.    49. public void save(final Feed feed) {    50. feed.setTitle(tfTitle.getValue());    51. feed.setDescription(taDescription.getValue());    52. feed.setLink(tfLink.getValue());    53.    54. final FeedServiceAsync feedService = Registry    55. .get(RSSReaderConstants.FEED_SERVICE);    56. feedService.saveFeed(feed, new AsyncCallback() {    57. @Override    58. public void onFailure(Throwable caught) {    59. Info.display("RSS Reader",    60. "Failed to save feed: " + feed.getTitle());    61. }    62.    63. @Override    64. public void onSuccess(Void result) {    65. Info.display("RSS Reader", "Feed " + feed.getTitle()    66. + " saved sucessfully");    67. }    68. });    69.    70. }    71.    72. }     最后当用户录入数据后,提交到 server 端处理。就会在控制台显示如下格式  的 xml 内容:   [html] view plaincopyprint?   1.     2.     3. Example Feed    4. This is an example feed    5. http://www.example.com/    6.     7.        优化 LinkFeedPopup   还记得 LinkFeedPopup 窗口,用来提供用户输入 URL 的页面吗?现在要给他优化一下,加  入 validation 的代码。   [java] view plaincopyprint?   1. package com.danielvaughan.rssreader.client.components;    2.    3. import com.extjs.gxt.ui.client.Style.LayoutRegion;    4. import com.extjs.gxt.ui.client.event.ButtonEvent;    5. import com.extjs.gxt.ui.client.event.ComponentEvent;    6. import com.extjs.gxt.ui.client.event.KeyListener;    7. import com.extjs.gxt.ui.client.event.SelectionListener;    8. import com.extjs.gxt.ui.client.util.Margins;    9. import com.extjs.gxt.ui.client.widget.Popup;    10. import com.extjs.gxt.ui.client.widget.Text;    11. import com.extjs.gxt.ui.client.widget.button.Button;    12. import com.extjs.gxt.ui.client.widget.form.TextField;    13. import com.extjs.gxt.ui.client.widget.layout.BorderLayout;    14. import com.extjs.gxt.ui.client.widget.layout.BorderLayoutData;    15. import com.google.gwt.event.dom.client.KeyCodes;    16. import com.google.gwt.user.client.Element;    17. import com.google.gwt.user.client.Window;    18.    19. public class LinkFeedPopup extends Popup {    20. private final TextField tfUrl = new TextField();    21.    22. public LinkFeedPopup() {    23. setSize(300, 55);    24. setBorders(true);    25. setShadow(true);    26. setAutoHide(false);    27. }    28.    29. public void addFeed(String url) {    30. Window.alert("We would now attempt to add " + url + " at this point");    31. }    32.    33. @Override    34. protected void onRender(Element parent, int pos) {    35. super.onRender(parent, pos);    36. final Text txtExplaination = new Text("Enter a feed url");    37. final Button btnAdd = new Button("add");    38. btnAdd.addSelectionListener(new SelectionListener() {    39.    40. public void componentSelected(ButtonEvent ce) {    41. if (tfUrl.isValid())    42. addFeed(tfUrl.getValue());    43. }    44. });    45.    46. tfUrl.addKeyListener(new KeyListener() {    47. public void componentKeyDown(ComponentEvent event) {    48. if (event.getKeyCode() == KeyCodes.KEY_ENTER) {    49. addFeed(tfUrl.getValue());    50. }    51. }    52. });    53. tfUrl.setAllowBlank(false);    54. tfUrl.setRegex("^http\\://[a-zA-Z0-9\\-\\.]+\\.[a-zA-Z]{2,3}(/\\S*)?$"  );    55. tfUrl.setAllowBlank(false);    56. tfUrl.getMessages().setBlankText(    57. "Please enter the URL of an existing feed");    58. tfUrl.setAutoValidate(true);    59. tfUrl    60. .getMessages()    61. .setRegexText(    62. "The link field must be a URL e.g. http://www.example.com/rss.xml");    63.    64. final BorderLayout layout = new BorderLayout();    65. setLayout(layout);    66.    67. final BorderLayoutData northData = new BorderLayoutData(    68. LayoutRegion.NORTH, 20);    69. northData.setMargins(new Margins(2));    70. add(txtExplaination, northData);    71.    72. final BorderLayoutData centerData = new BorderLayoutData(    73. LayoutRegion.CENTER);    74. centerData.setMargins(new Margins(2));    75. add(tfUrl, centerData);    76.    77. final BorderLayoutData eastData = new BorderLayoutData(    78. LayoutRegion.EAST, 50);    79. eastData.setMargins(new Margins(2, 2, 2, 20));    80. add(btnAdd, eastData);    81. }    82.    83. }             第四章:Data 与 Components   本章我们要学习,GXT 是如何允许我们和 Data 协同工作的。我们会着眼与 components 的  检索,操作,加工 data 的能力。并且学习具有操作数据能力的 components。      本章,我们会涉及到如下 GXt 功能集   Data    ModelData    BeanModel    BeanModelTag    BeanModelMarker    BeanModelFactory    Stores   Remote Data    DataProxies    DataReaders    ListLoadResults    ModelType    Loaders    LoadConfigs   Data-backed components    ListField    ComboBox    Grid   o ColumnModel   o ColumnConfig   o GridCellRenderer   操作数据(Data)   AJAX 应用,包括用 GXT 搭建的应用程序,其中一个最大的优势在于——在浏览器中可以  直接操作数据。GXT 提供了非常有用的可以操作数据的 components(Data-backed   components),可以允许用户直接通过他们操作数据。比如 ListField,ComboBox,Grid  等 components。通过使用这些 components,可以方便快捷的对数据进行过滤,排序,编  辑内容等操作。   当然还有另一组非可视化的控件集合:负责获得远程数据,缓存客户端数据,传递数据给可  视化 components。   我们这章都会涉及到他们。但是,首先我们要创建一组 data,作为可视化 components  (Data-backed components)的数据源。      ModelData 接口   GXT 提供了 ModelData 接口。data-backed components 使用的任何 data objects 必须实现  此接口。比如查看一下 GXT 中 ListField 的类定义,就明白此话的含义了:   [java] view plaincopyprint?   1. public class ListField extends Field implements   SelectionProvider    ModelData 接口提供了执行期间检索 names 和 values 的能力。GWT 不支持反射,而 GXT  则使用反射机制。摘自 ModelData 接口里的注释 :   [java] view plaincopyprint?   1. /**    2. * Primary interface for GXT model objects without support events. Models    3. * support a form of "introspection" as property names and values can be    4. * retrieved at runtime.    5. */       在我们 RSSReader 项目里,目前我们使用的 Feed JavaBean 去存储 feed 数据。但是 Feed  类还没有实现 ModelData 接口,所以 Feed 类还不能够作为 data-backed components 的数  据源!   想要让一个 JaveBean 可以作为 data-backed   components 的数据源,有下面三种方法:   1.修改 Feed JaveBean,让其 extends com.extjs.gxt.ui.client.data.BaseModel。这种方法  不可取,因为改动量大,而且 keys 容易出错,看代码便知晓:      [java] view plaincopyprint?   1. public class Feed extends BaseModel {    2. public Feed () {    3. }    4. public Feed (String uuid) {    5. set("uuid", uuid);    6. }    7. public String getDescription() {    8. return get("description");    9. }    10. public String getLink() {    11. return get("link");    12. }    13. public String getTitle() {    14. return get("title");    15. }    16. public String getUuid() {    17. return get("uuid");    18. }    19. public void setDescription(String description) {    20. set("description", description);    21. }    22. public void setLink(String link) {    23. set("link", link);    24. }    25. public void setTitle(String title) {    26. set("title", title);    27. }    28. }       2.修改 Feed JaveBean,让其 implements com.extjs.gxt.ui.client.data.BeanModelTag。具  体代码如下:   [java] view plaincopyprint?   1. public class Feed implements Serializable, BeanModelTag {    2. private String description;    3. private String link;    4. private String title;    5. private String uuid;    6. public Feed() {    7. }    8. public Feed(String uuid) {    9. this.uuid = uuid;    10. }    11. public String getDescription() {    12. return description;    13. }    14. public String getLink() {    15. return link;    16. }    17. public String getTitle() {    18. return title;    19. }    20. public String getUuid() {    21. return uuid;    22. }    23. public void setDescription(String description) {    24. this.description = description;    25. }    26. public void setLink(String link) {    27. this.link = link;    28. }    29. public void setTitle(String title) {    30. this.title = title;    31. }    32. }       3.新建一个类 FeedBeanModel,实现 BeanModelMarker 接口,通过在 FeedBeanModel  类中的注解@BEAN,指定此类包装了 Feed JavaBean。此方法可以避免直接修改 Feed   JaveBean。我们推荐使用此方法。    Feed JavaBean 在 shared 包下,但是 BeanModel 相关的类,属于 client 端  的代码。所以新建包:com.danielvaughan.rssreader.client.model:   [java] view plaincopyprint?   1. package com.danielvaughan.rssreader.client.model;    2.    3. import com.extjs.gxt.ui.client.data.BeanModelMarker;    4. import com.extjs.gxt.ui.client.data.BeanModelMarker.BEAN;    5.    6. @BEAN(com.danielvaughan.rssreader.shared.model.Feed.class)    7. public class FeedBeanModel implements BeanModelMarker {    8. }     @BEAN(com.danielvaughan.rssreader.shared.model.Feed.class)就指定了  FeedBeanModel 是 Feed 的包装 BeanModel 类。      总结   简单明了的说,本章就是讲了如下意思:    如果想让 Data-backed components 的组件能够显示出来数据,其数据必须  是 ModelData。    想让一个普通的 JavaBean 成为 ModelData,有三种方法。    我们推荐第三种方法实现 ModelData——因为不需要修改原先的 JavaBean。    That's all      Stroes   在 GXT 中,Stroe,一个抽象类,用于穿梭于 client 端,提供 ModelData objects。Stroe 其  实就是为 data-backed components 存储数据的。   Stroe 有两种:TreeStore——为了使用于 Tree components;ListStore——为了存储 list 数  据的(被 ListField, ComboBox, Grid components 使用)。      创建一个 ListStore 对象用来存储我们上一节创建的 Feed BeanModel,代码如下:   [java] view plaincopyprint?   1. ListStore feedStore = new ListStore();       通过其泛型的定义,就可以显而易见,Store 就是用来存储 BeanModel 的。但是如果想往  Store 中放入 BeanModel,不能直接添加,需要借助 BeanModelFactory。BeanModelFactory  会把一个 Feed javaBean 转换成 BeanModel 的表现形式,之后才可以被添加到 Store 中。   我们要修改 RSSReader 项目,新建一个 Feed JavaBean 对象,将其转换成 BeanModel  的表现形式,添加到 ListStore 中。    在 RSSReaderConstants 类,加入一个 FEED_STORE,用来最为 Registry  注册的 key。   [java] view plaincopyprint?   1. public static final String FEED_STORE = "feedStore";        在 RSSReader.onModuleLoad 方法,加入 Registry   [java] view plaincopyprint?   1. public void onModuleLoad() {    2. Registry.register(RSSReaderConstants.FEED_SERVICE,    3. GWT.create(FeedService.class));    4. Registry.register(RSSReaderConstants.FEED_STORE, new    5. ListStore());    6. …    7. }     修改 FeedForm.save 方法中的,onSuccess 回调方法,加入从 registry 获得  feed store 的相关代码:   [java] view plaincopyprint?   1. public void save(final Feed feed) {    2. @Override    3. public void onSuccess(Void result) {    4. Info.display("RSS Reader", "Feed " + feed.getTitle()    5. + " saved successfully");    6. final ListStore feedStore = Registry    7. .get(RSSReaderConstants.FEED_STORE);    8. }    9. …    10. }        通过 Feed 生成 BeanModelFactory   [java] view plaincopyprint?   1. public void onSuccess(Void result) {    2. Info.display("RSS Reader", "Feed " + feed.getTitle() + " saved    3. successfully");    4. final ListStore feedStore =    5. Registry.get(RSSReaderConstants.FEED_STORE);    6. BeanModelFactory beanModelFactory =    7. BeanModelLookup.get().getFactory(feed.getClass());    8. }        最后,在通过 BeanModelFactory 去生成 Feed 的 BeanModel,添加到 feed   store   [java] view plaincopyprint?   1. public void onSuccess(Void result) {    2. Info.display("RSS Reader", "Feed " + feed.getTitle() + " saved    3. successfully");    4. final ListStore feedStore =    5. Registry.get(RSSReaderConstants.FEED_STORE);    6. BeanModelFactory beanModelFactory =    7. BeanModelLookup.get().getFactory(feed.getClass());    8. feedStore.add(beanModelFactory.createModel(feed));    9. }       Data-backed ListField   只要一个 data-backed component 和一个 Store 绑定到一起,Store 的任何变化,都会自动  的展现在 data-backed component。对于影响 Store 数据的事件如下:    Add    Clear    Data Changed    Filter    Remove    Sort    Update   在 RSSReader 例子中,要新建一个 ListField 的 Data-backed component。然后让 feed store  绑定到 ListField 身上,这样 feed store 的内容就会通过 ListField 可视化的组件展现出来。  当 feed store 有变化就会自动的影响 ListField 的显示效果。    新建包:com.danielvaughan.rssreader.client.lists,在此包下,新建 FeedList   extends LayoutContainer   [java] view plaincopyprint?   1. public class FeedList extends LayoutContainer {}        在构造函数里,设置 FitLayout   [java] view plaincopyprint?   1. public FeedList() {    2. setLayout(new FitLayout());    3. }        Override onRender 方法,新建一个 ListField   [java] view plaincopyprint?   1. @Override    2. protected void onRender(Element parent, int index) {    3. super.onRender(parent, index);    4. final ListField feedList = new    5. ListField();    6. }     在 onRender 方法,通过 Registry 获得 feed store   [java] view plaincopyprint?   1. final ListStore feedStore =   Registry.get(RSSReaderConstants.FEED_STORE);     让 feedList 和 feedStore 绑定在一起   [java] view plaincopyprint?   1. feedList.setStore(feedStore);     设置 feedList 显示的列——feedList 这个 Data-backed component,会自动  的找到 feedStore 里所有的 BeanModel(Feed JavaBean),然后通过 title  字符串,按照反射原理在 Feed 里查找以 title 为 key 的 value,将 value 按照  ListField 的形式展示出来,有多少条 Feed(BeanModel)在 store 中,就会  显示出来。   [java] view plaincopyprint?   1. feedList.setDisplayField("title");     将 feedList 可视化组件,加入到容器中。   [java] view plaincopyprint?   1. add(feedList)     在 RssNavigationPanel 构造函数里,加入如下代码,添加刚才新建 FeedList  类,加入到 west 导航区。   [java] view plaincopyprint?   1. setLayout(new FitLayout());    2. add(new FeedList());        最后运行程序,效果如下:                  总结:ModelData,Data-backed components,  Stroe 之间的关系    Data-backed components 的数据源是 Store    Store 中存储的数据必须是 ModelData    但是 ModelData 必须是通过 BeanModelFactory 生成的。    并不是任何一个普通的 JavaBean 就可以被 BeanModelFactory 生成为  ModelData 的。前提是 JavaBean 必须具有成为 ModelData 的能力,如何实  现,就得看前一节的内容。  http://blog.csdn.net/miqi770/article/details/7193740   Server 端的持久化   到目前为止,在 RSSReader 项目里,还没有任何持久化的操作。为了让我们项目更具有真  实性,现在我们要添加持久化的代码。但是为了让我们更专注于 client-side,持久化的逻  辑仅仅是最基本的操作——实现真实的持久化逻辑之前先加入持久化的接口,这样就可以为  了需要,可以在不改变接口调用的基础上,改用另外一种持久化操作的内容既可(比如  hibernate)。   目前,实现最初的持久化版本,步骤如下:    当添加新的 feed 实体时候,在 server-side 使用 xml 文件来存储。    xml 文件只有一个,我们会在这个文件里维护 feed 的列表。   持久化的代码不属于 GXT 的犯愁,因此我们可以把他以一个黑盒对待。在我们 RSSReader  项目里,要加入 Persistence 接口和实现此接口的 FilePersistence 类,他们的功能就是负  责存储和获得 RSS feed 信息的。      持久化一个存在的 Feed   在第二章,我们新建一个 Link feed button,目的是用来从 internet 里导入一个已经存在的  RSS feed 网络地址。接下来我们要在 FeedService 接口里,定义 addExistingFeed 方法,  用作持久层存储 RSS feed 的 URL 地址。此功能要被绑定到 LinkFeedPopup 的 add button  上。    在 FeedService 接口里,定义 addExistingFeed 方法,当然要传入一个 URL   参数   [java] view plaincopyprint?   1. void addExistingFeed(String feedUrl);        同样的会在 FeedServiceAsync 接口里,定义与之相关的异步回调方法   [java] view plaincopyprint?   1. void addExistingFeed(String feedUrl, AsyncCallback    2. callback);        修改 LinkFeedPopup 的 addFeed 方法,加入 addExistingFeed 回调方法的  调用,具体逻辑一看便知   [java] view plaincopyprint?   1. public void addFeed(final String feedUrl) {    2. final FeedServiceAsync feedService = Registry    3. .get(RSSReaderConstants.FEED_SERVICE);    4. feedService.addExistingFeed(feedUrl, new AsyncCallback() {    5. @Override    6. public void onFailure(Throwable caught) {    7. Info.display("RSS Reader", "Failed to add feed at: " + feedUrl);    8. }    9.    10. @Override    11. public void onSuccess(Void result) {    12. tfUrl.clear();    13. Info.display("RSS Reader", "Feed at " + feedUrl    14. + " added successfully");    15. hide();    16. }    17. });    18. }        FeedServiceImpl 类里,我们要加入日志,这里用简单的  java.util.logging.Logger   [java] view plaincopyprint?   1. private final static Logger LOGGER =   Logger.getLogger(FeedServiceImpl.class    2. .getName());        在 FeedServiceImpl 类里,加入 private 的方法 loadFeed,用来根据 feed 的  URL,在 JDOM 规范下,从 internet 里获得 RSS 的 XML 内容,返回 Feed  实体 bean   [java] view plaincopyprint?   1. private Feed loadFeed(String feedUrl) {    2. Feed feed = new Feed(feedUrl);    3. try {    4. SAXBuilder parser = new SAXBuilder();    5. Document document = parser.build(new URL(feedUrl));    6. Element eleRoot = document.getRootElement();    7. Element eleChannel = eleRoot.getChild("channel");    8. feed.setTitle(eleChannel.getChildText("title"));    9. feed.setDescription(eleChannel.getChildText("description"));    10. feed.setLink(eleChannel.getChildText("link"));    11. return feed;    12. } catch (IOException e) {    13. LOGGER.log(Level.SEVERE, "IO Error loading feed", e);    14. return feed;    15. } catch (JDOMException e) {    16. LOGGER.log(Level.SEVERE, "Error parsing feed", e);    17. return feed;    18. }    19. }        在 FeedServiceImpl 类里,加入 HashMap 的属性,用来存储 Feed 对象   [java] view plaincopyprint?   1. private Map feeds = new HashMap();     我们目前的持久化功能是依赖与 jdom-1.1.2.jar 包功能的实现,具体的持久化  代码如下,全部贴出来   [html] view plaincopyprint?   1. 持久化的配置文件:rssreader.properties(放在 src\下)    2. feed.file=feeds.txt    3. data.folder=data    4. base.url=http://127.0.0.1:8888       [java] view plaincopyprint?   1. package com.danielvaughan.rssreader.server.utils;    2.    3. import java.util.Set;    4.    5. import org.jdom.Document;    6.    7. public interface Persistence {    8.    9. public Set loadFeedList();    10.    11. public void saveFeedList(Set feedUrls);    12.    13. public void saveFeedXml(String uuid, Document document);    14.    15. public String getUrl(String uuid);    16. }       [java] view plaincopyprint?   1. package com.danielvaughan.rssreader.server.utils;    2.    3. import java.io.BufferedReader;    4. import java.io.BufferedWriter;    5. import java.io.DataInputStream;    6. import java.io.File;    7. import java.io.FileInputStream;    8. import java.io.FileWriter;    9. import java.io.IOException;    10. import java.io.InputStream;    11. import java.io.InputStreamReader;    12. import java.util.HashSet;    13. import java.util.Properties;    14. import java.util.Set;    15. import java.util.logging.Logger;    16.    17. import org.jdom.Document;    18. import org.jdom.output.Format;    19. import org.jdom.output.XMLOutputter;    20.    21. public class FilePersistence implements Persistence {    22.    23. private final static Logger LOGGER =   Logger.getLogger(FilePersistence.class    24. .getName());    25.    26. private String dataFolder;    27. private String feedFilePath;    28. private String baseUrl;    29.    30. private final Properties properties = new Properties();    31.    32. public FilePersistence() {    33. // Read properties file.    34. try {    35. ClassLoader loader = ClassLoader.getSystemClassLoader();    36. InputStream in = loader.getResourceAsStream("rssreader.properties");    37. properties.load(in);    38.    39. dataFolder = (String) properties.get("data.folder");    40. String feedFile = (String) properties.get("feed.file");    41. feedFilePath = dataFolder + "\\" + feedFile;    42. baseUrl = (String) properties.get("base.url");    43.    44. initDataFolder();    45. } catch (IOException e) {    46. LOGGER.severe("Unable to load properties file");    47. }    48. }    49.    50. private void initDataFolder() {    51. try {    52. File dataFolderFile = new File(dataFolder);    53. if (!dataFolderFile.exists()) {    54. dataFolderFile.mkdir();    55. }    56. File feedFile = new File(feedFilePath);    57. if (!feedFile.exists()) {    58. feedFile.createNewFile();    59. }    60. } catch (IOException e) {    61. LOGGER.severe("Error initialising data folder");    62. }    63. }    64.    65. @Override    66. public void saveFeedList(Set feedUrls) {    67. try {    68. FileWriter fileWriter = new FileWriter(feedFilePath);    69. BufferedWriter out = new BufferedWriter(fileWriter);    70. for (String feedUrl : feedUrls) {    71. String line = feedUrl.concat("\n");    72. out.write(line);    73. }    74. out.close();    75. } catch (Exception e) {    76. LOGGER.severe("Error: " + e.getMessage());    77. }    78. }    79.    80. @Override    81. public Set loadFeedList() {    82. Set feedUrls = new HashSet();    83. try {    84. FileInputStream fstream = new FileInputStream(feedFilePath);    85. DataInputStream in = new DataInputStream(fstream);    86. BufferedReader br = new BufferedReader(new InputStreamReader(in));    87. String strLine;    88. while ((strLine = br.readLine()) != null) {    89. feedUrls.add(strLine);    90. }    91. in.close();    92. return feedUrls;    93. } catch (Exception e) {    94. LOGGER.severe("Error: " + e.getMessage());    95. return feedUrls;    96. }    97. }    98.    99. @Override    100. public void saveFeedXml(String feedId, Document document) {    101. try {    102. File file = new File(generateFilePath(feedId));    103. XMLOutputter serializer = new XMLOutputter();    104. Format prettyFormat = Format.getPrettyFormat();    105. serializer.setFormat(prettyFormat);    106. FileWriter writer = new FileWriter(file);    107. serializer.output(document, writer);    108. writer.close();    109. } catch (IOException e) {    110. LOGGER.severe("Error: " + e.getMessage());    111. }    112. }    113.    114. private String generateFilePath(String feedId) {    115. return dataFolder + "/" + feedId + ".xml";    116. }    117.    118. @Override    119. public String getUrl(String feedId) {    120. String fileName = generateFilePath(feedId);    121. return baseUrl + "/" + fileName;    122. }    123. }        最后在 FeedServiceImpl 类里,实现 addExistingFeed 方法,调用刚才创建  的 loadFeed 方法,根据 feedUrl 返回 Feed 对象,再调用上面持久化类的  saveFeedList 方法,完成持久化操作   [java] view plaincopyprint?   1. @Override    2. public void addExistingFeed(String feedUrl) {    3. Feed loadResult = loadFeed(feedUrl);    4. if (loadResult.getTitle() != null) {    5. feeds.put(feedUrl, loadFeed(feedUrl));    6. persistence.saveFeedList(feeds.keySet());    7. }    8. }        创建本地的 RSS xml 文件用来读取:http://127.0.0.1:8888/rss2sample.xml。  在 WebContent\下创建 rss2sample.xml,内容如下:   [html] view plaincopyprint?   1.     2.     3.     4. Liftoff News    5. http://liftoff.msfc.nasa.gov/    6. Liftoff to Space Exploration.    7. en-us    8. Tue, 10 Jun 2003 04:00:00 GMT    9.    10. Tue, 10 Jun 2003 09:41:01 GMT    11. http://blogs.law.harvard.edu/tech/rss    12. Weblog Editor 2.0    13. editor@example.com    14. webmaster@example.com    15.     16.    17. Star City    18. http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp    19. How do Americans get ready to work with Russians aboard the   International Space Station? They take a crash course in culture, language   and protocol at Russia's Star   City.    20. Tue, 03 Jun 2003 09:39:21 GMT    21. http://liftoff.msfc.nasa.gov/2003/06/03.html#item573    22.    23.     24.     25. Sky watchers in Europe, Asia, and parts of Alaska and Canada   will experience a   partial eclipse of the Sun on Saturday, May 31st.    26. Fri, 30 May 2003 11:06:42 GMT    27. http://liftoff.msfc.nasa.gov/2003/05/30.html#item572    28.    29.     30.     31. The Engine That Does More    32. http://liftoff.msfc.nasa.gov/news/2003/news-VASIMR.asp    33. Before man travels to Mars, NASA hopes to design new engines   that will let us fly through the Solar System more quickly. The proposed   VASIMR engine would do that.    34. Tue, 27 May 2003 08:37:32 GMT    35. http://liftoff.msfc.nasa.gov/2003/05/27.html#item571    36.    37.     38.     39. Astronauts' Dirty Laundry    40. http://liftoff.msfc.nasa.gov/news/2003/news-laundry.asp    41. Compared to earlier spacecraft, the International Space   Station has many luxuries, but laundry facilities are not one of them.   Instead, astronauts have other options.    42. Tue, 20 May 2003 08:56:02 GMT    43. http://liftoff.msfc.nasa.gov/2003/05/20.html#item570    44.    45.     46.     47.            运行程序,会读取 rssreader.properties 配置文件初始化持久化的配置。当你  通过 Link feed button 成功添加一个真实的 URL link  (http://127.0.0.1:8888/rss2sample.xml)的时候,会在项目  WebContent\data\feeds.txt 文件里存储一行内容  http://127.0.0.1:8888/rss2sample.xml            GXT 之旅:第四章:Data 与 Components(4)——远程数据  (remote data)    分类: ExtGWT2012-01-27 14:29815 人阅读评论(0)收藏举报      使用 remote data   stores 的数据来源不单单可以从 client-side 获得(就是从 Registry 里获得),也可以通过  调用远程数据来获得。对于远程数据的加载和处理工作,GXT 已经提供了轻巧方便的手段。  她支持通过 HTTP 协议来检索 XML 或者 JSON 格式的数据,或者直接通过 GWT RPC 来  检索到 objects 对象。对于不同的数据源,GXT 提供了相应的机制。如果有必要的话,会把  原始的数据转换成 ModelData 并且自动的放入 store 里。   对于远程数据的处理过程,会涉及到几个 components(非可视化组件)。他们各尽其责,  协同工作完成远程数据的检索和加工等操作。    DataProxy—负责从数据源检索出原始的数据    DataReader—将检索出的原始数据转换成 client-side 可用的 ModelData    Loader—将转换好的 ModelData 自动的加载到 store 里,给 data-backed   components 使用      这个几个 components 的相互作用,协同工作图解如下:      DataProxy 接口   DataProxy 就是用来从数据源检索原始数据的。根据不同的协议类型,有具体的 proxy 代理  实现类,但他们是都是实现了 GXT 提供的 DataProxy 接口的。下面就大致介绍一下具体的  实现类   DataProxy 描述   HttpProxy 通过 GWT 的 Requestbuilder 传输同一个 server 的数据,读取 XML 或 JSON 格式的数据。   MemoryProxy 简单的通过指定的构造函数传递数据   PagingModelMemoryProxy 类似 MemoryProxy,但是支持在从 memory 中读取数据时分页   RpcProxy 使用 GWT RPC 来检索数据,允许其过程中通过 loader 将 javabean 数据转换   ScriptTagProxy   通过一个 URL 来检索其返回的数据,其 URL 可以是别的域名,而不是自己服务器运行的域名。但是只支  持 JSON   一旦我们使用 DataProxy 检索出来数据,其原始的数据不是 ModelData 格式的时候,我们  就需要使用 DataReader 给原始的数据转换成 ModelData,这样一来,转换后的数据才可以  被放入 Store,供给 components 使用。   DataReader 接口   DataReader 的作用是用来翻译原始数据,将其转换成 ModelData 类型的 object。针对不  用类型的原始数据,有对应的具体 Reader,但是他们都是实现 GXT 提供的 DataReader  接口。一个 DataReader 的返回值可能是下面列表当中的一个:    ModelData 类型的对象集合    一个实现了 ListLoadResult 接口的 object——在 ListLoadResult 接口里有个  一 getData()方法用来返回 ModelData object 的    一个 PagingLoadResult object——PagingLoadResult 扩展了  ListLoadResult 接口,加入了分页的功能,因此返回一组数据的子集。   下面是一组列表,介绍不同类型的具体的 DataReader 实现类      DataReader   输入数据类  型   转换时,使用的工具类 输入数据类型 何时使用此 Reader   ModelReader ModelData   输入的已经是 ModelData,不需要何人  转换,只要放入到 ListLoadResult 即可   ListLoadResult   当加载的原始数据已经继承  BeanModel   BeanModelReader   JavaBean 的  集合   BeanModelFactory ListLoadResult   当加载的原始数据是普通的  javabean,需要使用他将其转换  成 BeanModel   JsonReader JSON 数据 ModelType 定义 ModelData 结果集 当原始数据是 Json 类型时   JsonLoadResult   Reader   JSON 数据 ModelType 定义 ListLoadResult 当原始数据是 Json 类型时   JsonPagingLoadResult   Reader   JSON 数据 ModelType 定义 PagingLoadResult 当原始数据是 Json 类型时   XmlReader XML 数据 ModelType 定义 ModelData 结果集 当原始数据是 xml 类型时   XmlLoadResultReader XML 数据 ModelType 定义 ListLoadResult 当原始数据是 xml 类型时   XmlPagingLoadResult   Reader   XML 数据 ModelType 定义 PagingLoadResult 当原始数据是 xml 类型时   当然还有 TreeModelReader 用来转换树结构的数据的,以后会讲到。   上面的列表大家会注意到“ModelType 定义”这句话。ModelType 是在 Reader 在进行转换的  时候会用到的类。      ModelType 的使用   ModelType 是用来定义原始数据的结构类型的。定要其原始数据的结构之后,reader 就会  根据此结构进行读取转换。   拿 XML 类型的数据来说,其结构比如:      [html] view plaincopyprint?   1.     2.     3. The best book in the world    4.     5.     6. The worst book in the world    7.     8.        那么就上面的结构,ModelType 应该有如下定义:   [java] view plaincopyprint?   1. final ModelType modelType = new ModelType();    2. modelType.setRoot("books");    3. modelType.setRecordName("book");    4. modelType.addField("title");       跟节点是 books,book 是记录集,title 是具体的一个 field 属性。      ModelType 当然也支持定义 Json 数据类型的结构。如下的 Json 数据,ModelType 的定义  同上   [html] view plaincopyprint?   1. {    2. "books": [    3. {    4. "book": {    5. "title": "The best book in the world"    6. },    7. "book": {    8. "title": "The worst book in the world"    9. }    10. }    11. ]    12. }       Loader 接口   1. 当 DataProxy 将原始数据获得,   2. 数据被 DataReader 转换成 ModelData 之后   3. 就需要使用 Loaders,将 ModelData 装入 Store   Loader 是所有接口根接口,与之对应的 BaseLoader 是所有抽象类的根抽象类。当然  BaseLoader 实现了 Loader 接口。请看下图:      Loader 一共就分两类:一个是 List 一个是 Tree。打开源码一看便知之前的关系。   Loaders 可以在数据加载的时候,将其排序,可以功过 setSortField,setSortDir 方法进行  设置,当然也可以通过 LoadConfig 对象来设置。      LoadConfig   LoadConfig 配置了数据是如何被 Loader 到 store 的。Loadconfig 接口有一系列的实现类  (BaseGroupingLoadConfigBasePagingLoadConfig),看名称就猜到大概意思这里就不详  细介绍了。我自己也没怎么研究。。。      重新屡屡思路   1. GXT 所能使用的 classes 必须要直接或间接的实现了 ModelData 接口,才可  以放入 Store 里   2. Store 里面存储的 ModelData,其实是充当的 client-side 的缓存层,  Data-backed components 直接使用的是 Store   3. 用 DataProxy 将远程的原始数据获得   4. ModelType 定义了远程数据的结构体   5. DataReader 根据 ModelType 的描述,把原始的数据转换成 ModelData   6. Loader 按照 DataProxy 和 DataReader 的定义,一气呵成,执行之后将数据  装入 Store。   7. Loadconfig 的定义告诉 Loader 对 ModelData 数据如何排序,分组,分页等  配置。      将上面提到的所有内容应用到 RSSReader 项目里    编辑 com.danielvaughan.rssreader.client.listsFeedList 类的 onRender 方法   [java] view plaincopyprint?   1. package com.danielvaughan.rssreader.client.lists;    2.    3. import java.util.List;    4.    5. import com.danielvaughan.rssreader.client.RSSReaderConstants;    6. import com.danielvaughan.rssreader.client.services.FeedServiceAsync;    7. import com.danielvaughan.rssreader.shared.model.Feed;    8. import com.extjs.gxt.ui.client.Registry;    9. import com.extjs.gxt.ui.client.data.BaseListLoader;    10. import com.extjs.gxt.ui.client.data.BeanModel;    11. import com.extjs.gxt.ui.client.data.BeanModelReader;    12. import com.extjs.gxt.ui.client.data.ListLoadResult;    13. import com.extjs.gxt.ui.client.data.ListLoader;    14. import com.extjs.gxt.ui.client.data.RpcProxy;    15. import com.extjs.gxt.ui.client.store.ListStore;    16. import com.extjs.gxt.ui.client.widget.LayoutContainer;    17. import com.extjs.gxt.ui.client.widget.form.ListField;    18. import com.extjs.gxt.ui.client.widget.layout.FitLayout;    19. import com.google.gwt.user.client.Element;    20. import com.google.gwt.user.client.rpc.AsyncCallback;    21.    22. public class FeedList extends LayoutContainer {    23.    24. public FeedList() {    25. setLayout(new FitLayout());    26. }    27.    28. @Override    29. protected void onRender(Element parent, int index) {    30. super.onRender(parent, index);    31. final ListField feedList = new ListField();    32. //0:从 Registry 里获得 Service    33. final FeedServiceAsync feedService = (FeedServiceAsync) Registry    34. .get(RSSReaderConstants.FEED_SERVICE);    35. //1:定义 proxy 在 load 方法里嗲用 Serivce 里的方法    36. RpcProxy> proxy = new RpcProxy>() {    37. @Override    38. protected void load(Object loadConfig,    39. AsyncCallback> callback) {    40. feedService.loadFeedList(callback);    41.    42. }    43. };    44. //2:定义 Reader    45. BeanModelReader reader = new BeanModelReader();    46. //3:将 proxy 和 reader 传入,定义 loader    47. ListLoader> loader = new   BaseListLoader>(    48. proxy, reader);    49. //4:传入 loader,生成 store,此时还没有 load 数据    50. ListStore feedStore = new ListStore(loader);    51. //5:将 stroe 绑定到 data-backed component 身上    52. feedList.setStore(feedStore);    53. feedList.setDisplayField("title");    54. //6:真正的 load 数据,load 成功之后,data-backed component 会自动的显示出来。    55. loader.load();    56.    57. add(feedList);    58. }    59. }          GXT 之旅:第四章:Data 与 Components(5)——Grid    分类: ExtGWT2012-01-30 15:24806 人阅读评论(2)收藏举报   准备工作——Item   正如我们所知,一个 feed 的信息,里面会包含多组 Item 的信息,也就是一对多的概念。如  果想将这些 items 都显示出来,我们需要有另外的 model bean class 来存储他们。整个处  理过程如下:    新建 Item modalData 类,其属性用来存储对应的信息(在这里为了简单起见,  直接使用之前提到的第一种方法,让 Item 成为 ModalData 类。):   [java] view plaincopyprint?   1. package com.danielvaughan.rssreader.shared.model;    2.    3. import com.extjs.gxt.ui.client.data.BaseModel;    4.    5. @SuppressWarnings("serial")    6. public class Item extends BaseModel {    7.    8. public Item() {    9.    10. }    11.    12. public String getCategory() {    13. return get("category");    14. }    15.    16. public String getDescription() {    17. return get("description");    18. }    19.    20. public String getLink() {    21. return get("link");    22. }    23.    24. public String getTitle() {    25. return get("title");    26. }    27.    28. public void setCategory(String category) {    29. set("category", category);    30. }    31.    32. public void setDescription(String description) {    33. set("description", description);    34. }    35.    36. public void setLink(String link) {    37. set("link", link);    38. }    39.    40. public void setTitle(String title) {    41. set("title", title);    42. }    43. }     在 FeedService 接口里,定义 loadItems 方法,根据 url,返回 Item List    [java] view plaincopyprint?   1. package com.danielvaughan.rssreader.client.services;    2.    3. import java.util.List;    4.    5. import com.danielvaughan.rssreader.shared.model.Feed;    6. import com.danielvaughan.rssreader.shared.model.Item;    7. import com.google.gwt.user.client.rpc.RemoteService;    8. import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;    9.    10. @RemoteServiceRelativePath("feed-service")    11. public interface FeedService extends RemoteService {    12. void addExistingFeed(String feedUrl);    13.    14. Feed createNewFeed();    15.    16. List loadFeedList();    17.    18. List loadItems(String feedUrl);    19.    20. void saveFeed(Feed feed);    21. }        同样的 FeedServiceAsync 异步回调类,添加对应的回调方法   [java] view plaincopyprint?   1. package com.danielvaughan.rssreader.client.services;    2.    3. import java.util.List;    4.    5. import com.danielvaughan.rssreader.shared.model.Feed;    6. import com.danielvaughan.rssreader.shared.model.Item;    7. import com.google.gwt.user.client.rpc.AsyncCallback;    8.    9. public interface FeedServiceAsync {    10. void addExistingFeed(String feedUrl, AsyncCallback callback);    11.    12. void createNewFeed(AsyncCallback callback);    13.    14. void loadFeedList(AsyncCallback> asyncCallback);    15.    16. void loadItems(String feedUrl, AsyncCallback> callback);    17.    18. void saveFeed(Feed feed, AsyncCallback callback);    19. }        在 FeedServiceImpl 实现类,实现抽象方法 loadItems   [java] view plaincopyprint?   1. @SuppressWarnings("unchecked")    2. public List loadItems(String feedUrl) {    3. List items = new ArrayList();    4. try {    5. SAXBuilder parser = new SAXBuilder();    6. Document document = parser.build(new URL(feedUrl));    7. Element eleRoot = document.getRootElement();    8. Element eleChannel = eleRoot.getChild("channel");    9. List itemElements = eleChannel.getChildren("item");    10. for (Element eleItem : itemElements) {    11. Item item = new Item();    12. item.setTitle(eleItem.getChildText("title"));    13. item.setDescription(eleItem.getChildText("description"));    14. item.setLink(eleItem.getChildText("link"));    15. item.setCategory(eleItem.getChildText("category"));    16. items.add(item);    17. }    18. return items;    19. } catch (IOException e) {    20. e.printStackTrace();    21. return items;    22.    23. } catch (JDOMException e) {    24. e.printStackTrace();    25. return items;    26. }    27. }    Grid   Grid 很类似与 flex 的 datagrid。GXT 的 Grid component 拥有这许多不同的功能。现在,  我们就从最基本的功能——如何的让 Grid 显示数据开始。当通过 Grid 的构造函数创建实例  的之后,需要指定两方面的内容:ListStore 和 ColumnModel   [java] view plaincopyprint?   1. Grid grid = new Grid(itemStore, columnModel);    ColumnConfig   Columnconfig 定义了 Grid 在显示的时候每一列。具体点说就是指明了:1.使用了列数据;  2.数据是如何被渲染出来的。   Conlumnconfig 和 ColumnModel 之间的关系,查看源码的构造函数一看便知:   [java] view plaincopyprint?   1. public ColumnModel(List columns) {    2. this.configs = new ArrayList(columns);    3. }       Columnconfig 可以有很多组,存入 list 里,然后传入到 ColumnModel 的构造函数中去,生  成 ColumnModel 的实例。   那么将上面提到所有内容加入到我们的 RSSReader 项目里——让应用程序在启动的时候,  自动的读取一个 RSS url 将读取的 items 信息显示到 Grid 中:    创建新 package:com.danielvaughan.rssreader.client.grids,在新包内加入  ItemGrid 内的自定义组件。我们将在这个 ItemGrid 内加入 Grid 的代码    为了让 Grid 能够自适应浏览器窗口的大小,ItemGrid 类要继承  LayoutContainer    习惯上,构造函数里,设置其 LayOut   [java] view plaincopyprint?   1. public ItemGrid() {    2. setLayout(new FitLayout());    3. }     当然了,必须要 override onRender 方法。首先,定义 ColumnConfigs 他是  一个 List   [java] view plaincopyprint?   1. final List columns = new ArrayList();    2. columns.add(new ColumnConfig("title", "Title", 200));    3. columns.add(new ColumnConfig("description", "Description", 200));     将定义好的 columns 传入 ColumnModel 的构造函数   [java] view plaincopyprint?   1. final ColumnModel columnModel = new ColumnModel(columns);        定义测试的 RSS url,为了保证定义的 url 一定好用:   [java] view plaincopyprint?   1. final String TEST_DATA_FILE = "http://127.0.0.1:8888/rss2sample.xml";        从 Registry 里拿 feedService   [java] view plaincopyprint?   1. final FeedServiceAsync feedService =   Registry.get(RSSReaderConstants.FEED_SERVICE);     还是按照之前的远程调用,RpcProxy 去调用 FeedService 的 loadItems 方法。  通过   [java] view plaincopyprint?   1. final FeedServiceAsync feedService =   Registry.get(RSSReaderConstants.FEED_SERVICE);    2. RpcProxy> proxy = new RpcProxy>() {    3. @Override    4. protected void load(Object loadConfig,    5. AsyncCallback> callback) {    6. feedService.loadItems(TEST_DATA_FILE, callback);    7. }    8. };     因为 Item 是使用的一种方法实现的 modelData——直接继承了 BaseModel  (其属性都是用 get/set 写的),因此不需要 reader 进行转换。loader 可以  直接通过 proxy 获得   [java] view plaincopyprint?   1. ListLoader> loader = new   BaseListLoader>(proxy);     通过 loader,获得 store 数据   [java] view plaincopyprint?   1. ListStore itemStore = new ListStore(loader);        ListStore 和 ColumnModel 都准备完毕之后,开始创建 Grid,将两个对象传  入 Grid 的构造函数当中。设置 description 列可以自动伸展,填充空白区域。   [java] view plaincopyprint?   1. Grid grid = new Grid(itemStore,    2. columnModel);    3. grid.setBorders(true);    4. grid.setAutoExpandColumn("description");     调用 load 方法,让 Grid 装载 store   [java] view plaincopyprint?   1. loader.load();     将 Grid 添加到 LayoutContainer 内显示   [java] view plaincopyprint?   1. add(grid);     最后编辑 com.danielvaughan.rssreader.client.components.RssMainPanel,  添加 ItemGrid   [java] view plaincopyprint?   1. package com.danielvaughan.rssreader.client.components;    2.    3. import com.danielvaughan.rssreader.client.grids.ItemGrid;    4. import com.extjs.gxt.ui.client.widget.ContentPanel;    5. import com.extjs.gxt.ui.client.widget.layout.FitLayout;    6.    7. public class RssMainPanel extends ContentPanel {    8. public RssMainPanel() {    9. setHeading("Main");    10. setLayout(new FitLayout());    11. add(new ItemGrid());    12. }    13. }             GXT 之旅:第四章:Data 与 Components(6)——  GridCellRenderer    分类: ExtGWT2012-01-31 10:34605 人阅读评论(1)收藏举报   GridCellRenderer(渲染器)   目前看来,RSSReader 项目的 grid 已经简单的显示出来了——每一列都是显示简单的一个  field 数据。如果想要让几个 fields 联合起来,更丰富的显示的话,我们就需要使用  GridCellRenderer。它允许我们使用 HTML 去渲染表格里面的内容。   GridCellRenderer 定义好之后,需要通过 ColumnConfig.setRenderer 方法设置,应用于一  个列上。   接下来我们要使用 GridCellRenderer 在我们 RSSReader 项目的自定义 component   ItemGrid。用来替换先前定义的 title 和 description 两个列,使用 GridCellRenderer 来显示  上下结构的(title 在上,description 在下)一列。    ItemGrid 的 onRender 方法里,定义一个 GridCellRenderer   [java] view plaincopyprint?   1. GridCellRenderer itemsRenderer = new   GridCellRenderer() {    2. @Override    3. public Object render(ModelData model, String property,    4. ColumnData config, int rowIndex, int colIndex,    5. ListStore store, Grid grid) {    6. String title = model.get("title");    7. String description = model.get("description");    8. return "" + title + "
" + description; 9. } 10. };  实现了 render 方法,通过 model 获得 title 和 description,联合形成一个 html 的 String  新建一个 ColumnConfig,将定义好的 itemsRenderer 设置进去,同时定义了 一列名为 Items,删除之前的 ColumnConfig [java] view plaincopyprint? 1. ColumnConfig column = new ColumnConfig(); 2. column.setId("items"); 3. column.setRenderer(itemsRenderer); 4. column.setHeader("Items");  将 column 加入到配置列表中,将配置列表放入 ColumnModel 里。 [java] view plaincopyprint? 1. columns.add(column); 2. final ColumnModel columnModel = new ColumnModel(columns);  因为只有一列,所以让 items 这一列伸展出来 [java] view plaincopyprint? 1. grid.setAutoExpandColumn("items");  运行程序之后的效果如下:  当我们再把先前的 title 和 description 列定义加入之后就会更清楚 ColumnConfig 意义,整个代码如下: [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.grids; 2. 3. import java.util.ArrayList; 4. import java.util.List; 5. 6. import com.danielvaughan.rssreader.client.RSSReaderConstants; 7. import com.danielvaughan.rssreader.client.services.FeedServiceAsync; 8. import com.danielvaughan.rssreader.shared.model.Item; 9. import com.extjs.gxt.ui.client.Registry; 10. import com.extjs.gxt.ui.client.data.BaseListLoader; 11. import com.extjs.gxt.ui.client.data.ListLoadResult; 12. import com.extjs.gxt.ui.client.data.ListLoader; 13. import com.extjs.gxt.ui.client.data.ModelData; 14. import com.extjs.gxt.ui.client.data.RpcProxy; 15. import com.extjs.gxt.ui.client.store.ListStore; 16. import com.extjs.gxt.ui.client.widget.LayoutContainer; 17. import com.extjs.gxt.ui.client.widget.grid.ColumnConfig; 18. import com.extjs.gxt.ui.client.widget.grid.ColumnData; 19. import com.extjs.gxt.ui.client.widget.grid.ColumnModel; 20. import com.extjs.gxt.ui.client.widget.grid.Grid; 21. import com.extjs.gxt.ui.client.widget.grid.GridCellRenderer; 22. import com.extjs.gxt.ui.client.widget.layout.FitLayout; 23. import com.google.gwt.user.client.Element; 24. import com.google.gwt.user.client.rpc.AsyncCallback; 25. 26. public class ItemGrid extends LayoutContainer { 27. 28. public ItemGrid() { 29. setLayout(new FitLayout()); 30. } 31. 32. @Override 33. protected void onRender(Element parent, int index) { 34. super.onRender(parent, index); 35. 36. final List columns = new ArrayList(); 37. 38. columns.add(new ColumnConfig("title", "Title", 200)); 39. columns.add(new ColumnConfig("description", "Description", 200)); 40. 41. GridCellRenderer itemsRenderer = new GridCellRenderer() { 42. @Override 43. public Object render(ModelData model, String property, 44. ColumnData config, int rowIndex, int colIndex, 45. ListStore store, Grid grid) { 46. String title = model.get("title"); 47. String description = model.get("description"); 48. return "" + title + "
" + description; 49. } 50. }; 51. ColumnConfig column = new ColumnConfig(); 52. column.setId("items"); 53. column.setRenderer(itemsRenderer); 54. column.setHeader("Items"); 55. columns.add(column); 56. final ColumnModel columnModel = new ColumnModel(columns); 57. final String TEST_DATA_FILE = "http://127.0.0.1:8888/rss2sample.xml"; 58. 59. final FeedServiceAsync feedService = Registry 60. .get(RSSReaderConstants.FEED_SERVICE); 61. RpcProxy> proxy = new RpcProxy>() { 62. @Override 63. protected void load(Object loadConfig, 64. AsyncCallback> callback) { 65. feedService.loadItems(TEST_DATA_FILE, callback); 66. } 67. }; 68. ListLoader> loader = new BaseListLoader>( 69. proxy); 70. ListStore itemStore = new ListStore(loader); 71. 72. Grid grid = new Grid(itemStore, columnModel); 73. grid.setBorders(true); 74. grid.setAutoExpandColumn("items"); 75. loader.load(); 76. add(grid); 77. } 78. }  ColumnConfig 其实就是定义一列。如果其不使用渲染其,则默认的简单文本 显示,如果使用渲染器,可以自定义显示效果支持 html 标签。显示的列的顺 序根 add 的顺序有关! GXT 之旅:第五章:高级 Components(1)——Trees 和 TreeGrid(1) 分类: ExtGWT2012-01-31 17:27752 人阅读评论(0)收藏举报 第五章:高级 Components 本章我们要基于前几章内容,更深入的学习 data-backed components。我们会学习 Tree 以及如何优化和改进数据的加载和显示等内容。学习如何将 Tree 的概念应用与 Grid。会涉 及到 Grid 的一些高级功能。最后我们要学习 menus 和 toolbars 本章,我们会涉及到如下 GXt 功能集  Trees  BaseTreeModel o TreeStore o TreePanel o TreeGrid o TreeGridCellRenderer  Advanced grid features o Column grouping . HeaderGroupConfig o Aggregation rows . AggregationRowConfig . SummaryType o Paging . PagingListResult . PagingLoadConfig . PagingModelMemoryProxy . PagingLoader . PagingToolBar  ImageBundle  Toolbars and menus o Menu o MenuItem o CheckMenuItem o MenuBar o MenuBarItem o MenuEvent o ToolBar o Status Trees 上一章,我们所涉及的 components 只是使用 ListData。现在我们要学习那些使用 TreesData 的 components。 GXT 所提供的有关 tree 的 components 和 list 的 components 是很类似的。不同的是,对 于 tree 来说,要使用其专门的 ModelData—Store, DataReader, 和 Loader。 BaseTreeModel BaseTreeModel 继承 BaseModel 实现了 TreeModel 接口,自然添加了关于 tree 的功能 方法——有关于管理父子关系的功能方法。为了让 ModelData 可以被 TreePanel 或者 TreeGrid 使用,javaBean 就需要继承 BaseTreeModel 而不是 BaseModel。 在 RSSReader 项目里,我们准备让一个 feed 的 url 的 items 集合存在一个树的分类上。为 了实现此功能,我们需要创建一个 Category class 继承 BaseTreeModel  在 com.danielvaughan.rssreader.shared.model 包下,新建类 Category,继 承 BaseTreeModel。 [java] view plaincopyprint? 1. public class Category extends BaseTreeModel {}  新建一个静态的 ID 属性,用来做自增序列。新建一个构造函数,其参数是 String 类型的 title,并且让 ID 自增 [java] view plaincopyprint? 1. private static int ID = 0; 2. 3. public Category(String title) { 4. set("id", ID++); 5. set("title", title); 6. }  再添加一个无参数的构造函数,只是让 ID 自增 [java] view plaincopyprint? 1. public Category() { 2. set("id", ID++); 3. }  在加入 get 方法,整个 Category 类的定义如下: [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.shared.model; 2. 3. import com.extjs.gxt.ui.client.data.BaseTreeModel; 4. 5. @SuppressWarnings("serial") 6. public class Category extends BaseTreeModel { 7. private static int ID = 0; 8. 9. public Category() { 10. set("id", ID++); 11. } 12. 13. public Category(String title) { 14. set("id", ID++); 15. set("title", title); 16. } 17. 18. public Integer getId() { 19. return (Integer) get("id"); 20. } 21. 22. public String getTitle() { 23. return (String) get("title"); 24. } 25. } 接下来,需要在 FeedService 接口里,定义新的方法,用来根据给定的 category 获得属于 该 category 内的 items 集合。  FeedService 接口内定义 loadCategorisedItems 方法 [java] view plaincopyprint? 1. List loadCategorisedItems(String feedUrl, Category category);  同样的,异步回调方法 [java] view plaincopyprint? 1. void loadCategorisedItems(String feedUrl, Category category, 2. AsyncCallback> callback);  在 FeedServiceImpl 类里,实现其抽象方法 [java] view plaincopyprint? 1. @Override 2. public List loadCategorisedItems(String feedUrl, 3. Category category) { 4. List items = loadItems(feedUrl); 5. Map> categorisedItems = new HashMap>(); 6. for (Item item : items) { 7. // 0:get each item 's category 8. String itemCategoryStr = item.getCategory(); 9. if (itemCategoryStr == null) { 10. itemCategoryStr = "Uncategorised"; 11. } 12. // 1: get categoryItems by itemCategoryStr 13. List categoryItems = categorisedItems.get(itemCategoryStr); 14. if (categoryItems == null) { 15. categoryItems = new ArrayList(); 16. } 17. // 2: add current item into categoryItems 18. categoryItems.add(item); 19. 20. // 3: put categoryItems into categorisedItems(hashMap) named as 21. // itemCategoryStr 22. categorisedItems.put(itemCategoryStr, categoryItems); 23. } 24. // 4: if category is null, return the whole categoryList 25. if (category == null) { 26. List categoryList = new ArrayList(); 27. for (String key : categorisedItems.keySet()) { 28. categoryList.add(new Category(key)); 29. } 30. return categoryList; 31. } else { 32. return new ArrayList(categorisedItems.get(category 33. .getTitle())); 34. } 35. } TreeStore TreeStore 是另外一个 Store 的实现类。其不同点在于,使用 TreeStore 来存储 ListStore, 用来表示层级的数据,而不是普通的数据集合。 我们可以将 TreeModel 添加到 TreeStore 里面,不需要使用 TreeModel 去管理父子层级关 系,不需要复杂的编码,系统内部会自动的管理其关系。对于如何通过 add 方法,将 TreeModel 添加到 TreeStore 中并同时设置之间的层级关系,之后会介绍。 TreePanel TreePanel 是一个真正的可视化树控件。使用起来也很简单,掌握起来也很容易。 当通过构造函数创建 TreePanel 的时候,其必须传入 TreeStore 的参数。通过 setDisplayProperty 方法,来设置显示 TreeStore 里面的哪一列内容。 默认情况下,当一个节点有子节点的时候,显示的图标是文件夹;如果是叶子节点的时候(就 是该节点没有子节点),显示的图标是白色文件图片。当然可以通过 setLeafIcon 来设置叶 子节点的图标(需要以 GWT 的 AbstractImagePrototype 类作为参数)。 ImageBundle Tree components 使用 GWT 提供的 ImageBundle 功能,用来作为 tree 节点的图标的初 始化加载过程。如果想要在我们 RSSReader 项目里,让 tree components 使用自定义的图 标的话,就需要我们定义一个 ImageBundle。尽管 ImageBundle 是 GWT 的内容并未 GXT 的内容,但是我们要注意的是:虽然 ImageBundle 的出现是用来替换 ClientBundle,但是 他还是不推荐被直接使用的(deprecation)。我们需要自加工一个 Icons。  新建包:com.danielvaughan.rssreader.client.resources,在其包下新建 Icons 接口,继承 ImageBundle [java] view plaincopyprint? 1. @SuppressWarnings("deprecation") 2. public interface Icons extends ImageBundle {}  每个被添加到 ImageBundle 的图片,都需要一个无参的方法返回 AbstractImagePrototype。使用@Resource 注解图片的位置,相对于当前 package 的路径 [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.resources; 2. 3. import com.google.gwt.user.client.ui.AbstractImagePrototype; 4. import com.google.gwt.user.client.ui.ImageBundle; 5. 6. @SuppressWarnings("deprecation") 7. public interface Icons extends ImageBundle { 8. @Resource("rss.png") 9. AbstractImagePrototype rss(); 10. }  在同样的 package 下,新建 Resources 类,用来静态引用刚才生成的 Icons。 [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.resources; 2. 3. import com.google.gwt.core.client.GWT; 4. 5. public class Resources { 6. public static final Icons ICONS = GWT.create(Icons.class); 7. }  最后,我们 RSSReader 项目的 package 层级关系如下: GXT 之旅:第五章:高级 Components(1)——Trees 和 TreeGrid(2) 分类: ExtGWT2012-02-01 10:34628 人阅读评论(1)收藏举报 TreeGrid TreeGrid 将 Trees 和 Grids 的功能结合在一起。整体上看是由多个 columns 组成的 Grid, 但是内容上看,支持显示树目录结构的数据。 他和 TreePanel 一样,使用的 Store 也是 TreeStore,ModelDate 也是 BaseTreeModel。 TreeGridCellRenderer TreeGridCellRenderer 实现了 GridCellRenderer,其功能顾名思义,用来渲染 TreeGrid 的 column 的。接下来我们要在 RSSReader 项目里使用 TreeGridCellRenderer,对于一个 renderer 来说可以被设置在任何一个 column 上。  在 package:com.danielvaughan.rssreader.client.grids 里,新建 ItemCategoryGrid,继承 LayoutContainer [java] view plaincopyprint? 1. public class ItemCategoryGrid extends LayoutContainer { 2. public ItemCategoryGrid() { 3. setLayout(new FitLayout()); 4. } 5. }  Override onRender 方法,在其方法内从 Registry 内获得 FeedService [java] view plaincopyprint? 1. protected void onRender(Element parent, int index) { 2. super.onRender(parent, index); 3. final FeedServiceAsync feedService = (FeedServiceAsync) Registry 4. .get(RSSReaderConstants.FEED_SERVICE); 5. }  通过 RpcProxy,调用 FeedService.loadCategorisedItems 方法。因为对于 Trees 或者 TreeGrid 的数据,每次数据加载都只加载当前 node 节点的 children,不是一股脑的将所有数据都加载到 client。所以每次的请求调用过 程,就都需要传入参数——loadConfig,那么对于当前的例子来说,loadConfig 就是 Category(BaseTreeModel)。 [java] view plaincopyprint? 1. final String TEST_DATA_FILE = "http://127.0.0.1:8888/rss2sample.xml"; 2. RpcProxy> proxy = new RpcProxy>() { 3. @Override 4. protected void load(Object loadConfig, 5. AsyncCallback> callback) { 6. feedService.loadCategorisedItems(TEST_DATA_FILE, 7. (Category) loadConfig, callback); 8. } 9. };  新建 BaseTreeLoader 对象,override hasChildren 方法。 [java] view plaincopyprint? 1. final TreeLoader loader = new BaseTreeLoader( 2. proxy) { 3. @Override 4. public boolean hasChildren(ModelData parent) { 5. if (parent instanceof Category) { 6. return true; 7. } else { 8. return false; 9. } 10. } 11. };  这里不需要使用 reader,因为 RpcProxy 所调用的远程方法,已经返回的是 ModelData。通过刚刚生成的 loader,传入 TreeStore 的构造函数。 [java] view plaincopyprint? 1. final TreeStore feedStore = new TreeStore(loader);  创建 ColumnConfig,使用 TreeGridCellRenderer 作为 Tree 的渲染器 [java] view plaincopyprint? 1. ColumnConfig title = new ColumnConfig("title", "Title", 200); 2. title.setRenderer(new TreeGridCellRenderer());  新建另外一个 ColumnConfig description,然后将 ColumnConfig title 和 description 以 list 的形式传入 ColumnModel [java] view plaincopyprint? 1. ColumnConfig description = new ColumnConfig("description","Description", 200); 2. ColumnModel columnModel = new ColumnModel(Arrays.asList(title, description));  定义 TreeGrid,传入刚刚生成 feedStore 和 columnModel。让 title 列自动伸 展,再设置上一节定义的 ICONs [java] view plaincopyprint? 1. TreeGrid treeGrid = new TreeGrid(feedStore,columnModel); 2. treeGrid.setBorders(true); 3. treeGrid.setAutoExpandColumn("title"); 4. treeGrid.getStyle().setLeafIcon(Resources.ICONS.rss());  通过 loader 的 load 方法,开始启动整个执行过程,将 treeGrid 加入 LayoutContainer [java] view plaincopyprint? 1. loader.load(); 2. add(treeGrid);  在 RssMainPanel 类的构造函数里,去掉先前 add 的 ItemGrid,替换为 add ItemCategoryGrid. [java] view plaincopyprint? 1. public RssMainPanel() { 2. setHeading("Main"); 3. setLayout(new FitLayout()); 4. add(new ItemCategoryGrid()); 5. }  最后运行程序如下:  整个代码如下: [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.grids; 2. 3. import java.util.Arrays; 4. import java.util.List; 5. 6. import com.danielvaughan.rssreader.client.RSSReaderConstants; 7. import com.danielvaughan.rssreader.client.resources.Resources; 8. import com.danielvaughan.rssreader.client.services.FeedServiceAsync; 9. import com.danielvaughan.rssreader.shared.model.Category; 10. import com.extjs.gxt.ui.client.Registry; 11. import com.extjs.gxt.ui.client.data.BaseTreeLoader; 12. import com.extjs.gxt.ui.client.data.ModelData; 13. import com.extjs.gxt.ui.client.data.RpcProxy; 14. import com.extjs.gxt.ui.client.data.TreeLoader; 15. import com.extjs.gxt.ui.client.store.TreeStore; 16. import com.extjs.gxt.ui.client.widget.LayoutContainer; 17. import com.extjs.gxt.ui.client.widget.grid.ColumnConfig; 18. import com.extjs.gxt.ui.client.widget.grid.ColumnModel; 19. import com.extjs.gxt.ui.client.widget.layout.FitLayout; 20. import com.extjs.gxt.ui.client.widget.treegrid.TreeGrid; 21. import com.extjs.gxt.ui.client.widget.treegrid.TreeGridCellRenderer; 22. import com.google.gwt.user.client.Element; 23. import com.google.gwt.user.client.rpc.AsyncCallback; 24. 25. public class ItemCategoryGrid extends LayoutContainer { 26. public ItemCategoryGrid() { 27. setLayout(new FitLayout()); 28. } 29. 30. @Override 31. protected void onRender(Element parent, int index) { 32. super.onRender(parent, index); 33. final FeedServiceAsync feedService = (FeedServiceAsync) Registry 34. .get(RSSReaderConstants.FEED_SERVICE); 35. final String TEST_DATA_FILE = "http://127.0.0.1:8888/rss2sample.xml"; 36. RpcProxy> proxy = new RpcProxy>() { 37. @Override 38. protected void load(Object loadConfig, 39. AsyncCallback> callback) { 40. feedService.loadCategorisedItems(TEST_DATA_FILE, 41. (Category) loadConfig, callback); 42. } 43. }; 44. 45. final TreeLoader loader = new BaseTreeLoader( 46. proxy) { 47. @Override 48. public boolean hasChildren(ModelData parent) { 49. if (parent instanceof Category) { 50. return true; 51. } else { 52. return false; 53. } 54. } 55. }; 56. 57. final TreeStore feedStore = new TreeStore(loader); 58. 59. ColumnConfig title = new ColumnConfig("title", "Title", 200); 60. ColumnConfig description = new ColumnConfig("description","Description", 200); 61. 62. title.setRenderer(new TreeGridCellRenderer()); 63. 64. ColumnModel columnModel = new ColumnModel(Arrays.asList(title,description)); 65. 66. TreeGrid treeGrid = new TreeGrid(feedStore, 67. columnModel); 68. treeGrid.setBorders(true); 69. treeGrid.setAutoExpandColumn("title"); 70. treeGrid.getStyle().setLeafIcon(Resources.ICONS.rss()); 71. //loader.load();//不需要 load 72. add(treeGrid);//当 treeGrid 添加之后,GXT 内部会自动的 loader.load();,如果 我们再手动的 load 的话,就会 call service 两次 73. } 74. } 注意:通过断点跟踪 FeedServiceImpl.loadCategorisedItems 方法。我们会发现,此方法会 被多次调用。整个现象如下:  TreeGrid 在初始化的时候(也就是 load 的时候),会调用一次 FeedServiceImpl.loadCategorisedItems 方法。数据显示的时候,文件夹图标 都是闭合的。  当闭合的图标第一次被打开的时候,会在调用一次 FeedServiceImpl.loadCategorisedItems 方法。此时,代码内部会自动的将当 前节点的 Category,传入到 proxy 的 load 方法中去,作为参数,让 FeedServiceImpl.loadCategorisedItems 方法去根据 Category,获得 items 数据。  当闭合的图标关闭后再打开的时候,就不会调用了。也就是说,第一次将内 容加载到 client 之后,就不会再发出请求了。 大家再看看源代码——TreeGridCellRenderer.render 方法。  当我们在新建 TreeGridCellRenderer 的时候,不需要 override render 方法, GXT 已经给写好了。直接 new 出对象既可。  TreeGrid 在初始化的时候(也就是 load 的时候),有多少条数据,就会调用 多少遍 TreeGridCellRenderer.render 方法。  当点击文件夹图标的节点的时候,也就会自动的调用 TreeGridCellRenderer.render 方法。 GXT 之旅:第五章:高级 Components(2)——Grid 的高级应 用 分类: ExtGWT2012-02-02 11:18625 人阅读评论(0)收藏举报 Grid 的高级应用 之前,我们学习的都是 Grid 的基本功能。事实上,Grids 提供了丰富的功能,下面就让我们 了解一下。 HeaderGroupConfig 假设我们想比较欧洲东部在 1950 和 2000 年之间的人口数,通常的显示效果如下: 但是我们想让表格更直观的显示,如下: 这时我们就需要对 GXT Grid 有如下步骤的加工:  针对每一列,新建 ColumnConfig  将所有的 ColumnConfigs 放入一个 list  通过 list 生成 ColumnModel [java] view plaincopyprint? 1. final List columns = new ArrayList(); 2. ColumnConfig column = new ColumnConfig("countryName", 3. "Country",100); 4. columns.add(column); 5. column = new ColumnConfig("population1950", "1950",130); 6. columns.add(column); 7. column = new ColumnConfig("population2000", "2000",130); 8. columns.add(column); 9. final ColumnModel columnModel = new ColumnModel(columns);  上述的代码,所呈现出的 Grid 如下:  如果希望将后面两列分组一组,我们就需要使用到 HeaderGroupConfig [java] view plaincopyprint? 1. HeaderGroupConfig headerGroupConfig = new HeaderGroupConfig( 2. "Population (000's)", 1, 2);  第一个参数是 title,第二和第三个参数分别是合并后的行数和合并后的列数。  将 headerGroupConfig add 在 ColumnModel 里。并指明行号和起始的列号 [java] view plaincopyprint? 1. columnModel.addHeaderGroup(0, 1, headerGroupConfig););  执行后的效果如下: AggregationRowConfig 使用 AggregationRowConfig,可以创建统计行,用来针对某些列的统计数据。 SummaryType 是用来设置其统计数据的算法:  SummaryType.SUM  SummaryType.AVG  SummaryType.MIN  SummaryType.MAX  SummaryType.COUNT 如果我们想实现上图的效果,我们应该实现的代码如下:  新建 AggregationRowConfig, [java] view plaincopyprint? 1. AggregationRowConfig> totals = new AggregationRowConfig 2. ();  通过 setHtml 方法,设置该行的题头 [java] view plaincopyprint? 1. totals.setHtml("countryName", "Total");  针对与要统计的列,我们需要设置其统计算法。 [java] view plaincopyprint? 1. totals.setSummaryType("population1950", SummaryType.SUM); 2. totals.setSummaryType("population2000", SummaryType.SUM);  对于要统计的列,为了显示的需要,要设置 NumberFormat (com.google.gwt.i18n.client.NumberFormat)。当然也可以通过 AggregationRenderer.实现,但是不管怎样,都需要使用其中一种方法,否 则,会空白显示 [java] view plaincopyprint? 1. totals.setSummaryFormat("population1950", NumberFormat. 2. getDecimalFormat()); 3. totals.setSummaryFormat("population2000", NumberFormat. 4. getDecimalFormat());  同样的,AggregationRowConfig 实例需要被添加到 columnModel 里 [java] view plaincopyprint? 1. columnModel.addAggregationRow(totals); GXT 之旅:第五章:高级 Components(3)——Paging 2012-02-02 16:22853 人阅读评论(3)收藏举报 Paging Paging 是 GXT 提供的非常有用的功能。顾名思义,就是分页显示数据,而不是一页显示所 有的数据。GXT 支持远程和本地的分页:远程分页就是真分页,每次 server 端返回数据都 是数据库里分页后的数据;本地的分页就是假分页,数据库一次性 load 全部数据后,前端 再分页内存里面的数据。为了方便起见,仅仅使用假分页,给大家展示一下 Paging 的功能。 介绍一下分页工作的相关类 PagingLoadResult 分页的数据,其必须被填充到 PagingLoadResult。PagingLoadResult 是 ListLoadResult 的扩展接口,提供了额外的功能(TotalLength 和 Offset), PagingLoadConfig PagingLoadConfig 封装了请求分页数据的参数,指定了 offset,数据返回起始点等。 接下来,我们要在 RSSReader 项目里,新建一个方法,返回 PagingLoadResult  FeedService 接口里,定义第二个 loadItems 方法, [java] view plaincopyprint? 1. PagingLoadResult loadItems(String feedUrl, final 2. PagingLoadConfig config);  FeedServiceAsync 接口里,定义其回调方法 [java] view plaincopyprint? 1. void loadItems(String feedUrl, PagingLoadConfig config, 2. AsyncCallback> callback);  FeedServiceImpl 里,实现这个抽象方法。 [java] view plaincopyprint? 1. @Override 2. public PagingLoadResult loadItems(String feedUrl, 3. PagingLoadConfig config) { 4. List items = loadItems(feedUrl); 5. return getPagingLoadResult(items, config); 6. }  getPagingLoadResult 的方法实现如下: [java] view plaincopyprint? 1. private PagingLoadResult getPagingLoadResult(List items, 2. PagingLoadConfig config) { 3. //定义 pageItems,存储 Item,作为返回的数据源 4. List pageItems = new ArrayList(); 5. //通过 PagingLoadConfig,获得相关参数(offset) 6. int offset = config.getOffset(); 7. //获得全部数据大小 8. int limit = items.size(); 9. //根据 offset 获得 limit 10. if (config.getLimit() > 0) { 11. limit = Math.min(offset + config.getLimit(), limit); 12. } 13. //定义好边界之后,开始读取数据 14. for (int i = offset; i < limit; i++) { 15. pageItems.add(items.get(i)); 16. } 17. //通过 pageItems,转化成 BasePagingLoadResult,同时赋值上 offset 和 totalLength 18. return new BasePagingLoadResult(pageItems, offset, items.size()); 19. 20. } PagingModelMemoryProxy 对于返回分页的数据的方法的代理 proxy 需要使用 PagingModelMemoryProxy,针对于 PagingModelMemoryProxy 的 loader 是 PagingLoader PagingLoader PagingLoader 的构造函数里,参数指明了类型是 PagingModelMemoryProxy, PagingLoader 会通过 PagingModelMemoryProxy,load 分页的数据集到 store 里。 [java] view plaincopyprint? 1. PagingLoader> loader = new BasePagingLoader>(proxy); 对于 load 数据的时候,需要指定 offset 和 pageSize [java] view plaincopyprint? 1. loader.load(0, 4);//public void load(int offset, int pageSize); 对于其过程使用的 store 是 ListStore,因为数据本身来说,就是一般的 list 数据集,分页的 操作只是从大的 list 数据集里面获取部分数据集。 PagingToolBar PagingToolBar extends ToolBar,预定义了相关的分页功能。 对于 PagingToolBar 来说,他数据源所绑定的是 PagingLoader,大致代码如下: [java] view plaincopyprint? 1. toolBar.bind(loader); 2. add(toolBar); 接下来,我们要在 RSSReader 项目里实现上面提到的所有概念。  新建 ItemPagingGrid 类,整个代码内容如下: [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.grids; 2. 3. import java.util.ArrayList; 4. import java.util.List; 5. 6. import com.danielvaughan.rssreader.client.RSSReaderConstants; 7. import com.danielvaughan.rssreader.client.services.FeedServiceAsync; 8. import com.danielvaughan.rssreader.shared.model.Item; 9. import com.extjs.gxt.ui.client.Registry; 10. import com.extjs.gxt.ui.client.data.BasePagingLoader; 11. import com.extjs.gxt.ui.client.data.ModelData; 12. import com.extjs.gxt.ui.client.data.PagingLoadConfig; 13. import com.extjs.gxt.ui.client.data.PagingLoadResult; 14. import com.extjs.gxt.ui.client.data.PagingLoader; 15. import com.extjs.gxt.ui.client.data.RpcProxy; 16. import com.extjs.gxt.ui.client.store.ListStore; 17. import com.extjs.gxt.ui.client.widget.ContentPanel; 18. import com.extjs.gxt.ui.client.widget.LayoutContainer; 19. import com.extjs.gxt.ui.client.widget.grid.ColumnConfig; 20. import com.extjs.gxt.ui.client.widget.grid.ColumnData; 21. import com.extjs.gxt.ui.client.widget.grid.ColumnModel; 22. import com.extjs.gxt.ui.client.widget.grid.Grid; 23. import com.extjs.gxt.ui.client.widget.grid.GridCellRenderer; 24. import com.extjs.gxt.ui.client.widget.layout.FitLayout; 25. import com.extjs.gxt.ui.client.widget.toolbar.PagingToolBar; 26. import com.google.gwt.user.client.Element; 27. import com.google.gwt.user.client.rpc.AsyncCallback; 28. 29. public class ItemPagingGrid extends LayoutContainer { 30. 31. private static final int PAGE_SIZE = 2; 32. 33. public ItemPagingGrid() { 34. setLayout(new FitLayout()); 35. } 36. 37. @Override 38. protected void onRender(Element parent, int index) { 39. super.onRender(parent, index); 40. // ColumnModel 41. final List columns = new ArrayList(); 42. GridCellRenderer itemsRenderer = new GridCellRenderer() { 43. @Override 44. public Object render(ModelData model, String property, 45. ColumnData config, int rowIndex, int colIndex, 46. ListStore store, Grid grid) { 47. String title = model.get("title"); 48. String description = model.get("description"); 49. return "" + title + "
" + description; 50. } 51. }; 52. ColumnConfig column = new ColumnConfig(); 53. column.setId("items"); 54. column.setRenderer(itemsRenderer); 55. column.setHeader("Items"); 56. columns.add(column); 57. final ColumnModel columnModel = new ColumnModel(columns); 58. 59. // Proxy: 60. final String TEST_DATA_FILE = "http://127.0.0.1:8888/rss2sample.xml"; 61. final FeedServiceAsync feedService = Registry 62. .get(RSSReaderConstants.FEED_SERVICE); 63. RpcProxy> proxy = new RpcProxy>() { 64. @Override 65. protected void load(Object loadConfig,// loadConfig 是 GXT 内部自动传入的 66. AsyncCallback> callback) { 67. feedService.loadItems(TEST_DATA_FILE, 68. (PagingLoadConfig) loadConfig, callback); 69. } 70. }; 71. // Loader:根据 proxy,生成 loader,因为返回的类型正好是 PagingLoadResult, 不需要数据转换 72. PagingLoader> loader = new BasePagingLoader>( 73. proxy); 74. // Store: 75. ListStore itemStore = new ListStore(loader); 76. // PagingToolBar:定义的时候需要传入分页大小 77. final PagingToolBar toolBar = new PagingToolBar(PAGE_SIZE); 78. toolBar.bind(loader); 79. // Grid:定义的时候,需要传入 store 和 columnModel 80. Grid grid = new Grid(itemStore, columnModel); 81. grid.setBorders(true); 82. grid.setAutoExpandColumn("items"); 83. loader.load();//此时的 load 应用了默认的 PagingLoadConfig 84. // Grid 不直接加入到 LayoutContainer,而是 Grid 添加到 ContentPanel, ContentPanel 添加到 LayoutContainer 85. ContentPanel panel = new ContentPanel(); 86. panel.setLayout(new FitLayout()); 87. panel.add(grid); 88. panel.setHeaderVisible(false); 89. panel.setBottomComponent(toolBar); 90. add(panel); 91. } 92. }  最后把 ItemPagingGrid 添加到 RssMainPanel 里,显示出来。注释掉之前的 components [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.components; 2. 3. import com.danielvaughan.rssreader.client.grids.ItemPagingGrid; 4. import com.extjs.gxt.ui.client.widget.ContentPanel; 5. import com.extjs.gxt.ui.client.widget.layout.FitLayout; 6. 7. public class RssMainPanel extends ContentPanel { 8. public RssMainPanel() { 9. setHeading("Main"); 10. setLayout(new FitLayout()); 11. // add(new ItemGrid()); 12. // add(new ItemCategoryGrid()); 13. add(new ItemPagingGrid()); 14. } 15. }  运行结果如下  如果想让 Grid 在加载数据的时候直接到第二页。可以使用:loader.load(2, PAGE_SIZE);  只要绑定了(toolBar.bind(loader);)loader 之后,其翻页操作不需要我们实 现,PagingToolBar 内部已经实现好了。 GXT 之旅:第五章:高级 Components(4)——Menus 2012-02-07 11:48401 人阅读评论(2)收藏举报 Menu component  Menu 是一个非常灵活的 component,他可以显示一组菜单,创建的大致代 码如下: [java] view plaincopyprint? 1. Menu contextMenu = new Menu(); 2. contextMenu.add(new MenuItem("Option 1")); 3. contextMenu.add(new MenuItem("Option 2")); 4. contextMenu.add(new MenuItem("Option 3")); 5. Label label = new Label("Menu appears here"); 6. contextMenu.show(label);  当然一个 Menu 可以被添加到一个 Button 上,来作为该 Button 的额外选项: [java] view plaincopyprint? 1. Menu contextMenu = new Menu(); 2. contextMenu.add(new MenuItem("Option 1")); 3. contextMenu.add(new MenuItem("Option 2")); 4. contextMenu.add(new MenuItem("Option 3")); 5. Button button = new Button("Menu"); 6. button.setMenu(contextMenu);  当一个 Menu 有很多的 Item,可以设置允许最大的高度(setMaxHeight), 让其超出的部分是可卷动的 [java] view plaincopyprint? 1. Menu contextMenu = new Menu(); 2. for (int i = 1; i < 100; i++) { 3. contextMenu.add(new MenuItem("Option " + i)); 4. } 5. contextMenu.setMaxHeight(200); 6. Button button = new Button("Menu"); 7. button.setMenu(contextMenu); MenuBar component  创建的 Menu 不单单可以添加到 Button 上,也可以添加到 MenuBar 上。多 组 Menu 添加到 MenuBar 上,就很像左面程序的菜单。但是 Menu 创建之后, 不能直接添加到 MenuBar 上,需要包装成 MenuBarItem [java] view plaincopyprint? 1. Menu menu1 = new Menu(); 2. menu1.add(new MenuItem("Option 1")); 3. menu1.add(new MenuItem("Option 2")); 4. menu1.add(new MenuItem("Option 3")); 5. Menu menu2 = new Menu(); 6. menu2.add(new MenuItem("Option 4")); 7. menu2.add(new MenuItem("Option 5")); 8. menu2.add(new MenuItem("Option 6")); 9. MenuBar menuBar = new MenuBar(); 10. menuBar.add(new MenuBarItem("Menu 1", menu1)); 11. menuBar.add(new MenuBarItem("Menu 2", menu2)); 12. viewport.add(menuBar); MenuItem component  下面详细介绍一个 MenuItem:Menu 就可以看成一个容器,用来包装 MenuItem,具体的显示效果还是通过 MenuItem 来设置的——text,icon 等 [java] view plaincopyprint? 1. Menu menu1 = new Menu(); 2. menu1.add(new MenuItem("Option 1",Resources.ICONS.page())); 3. menu1.add(new MenuItem("Option 2",Resources.ICONS.page())); 4. menu1.add(new MenuItem("Option 3",Resources.ICONS.page()));  一个 Menu 可以被设置成另外一个 Menu 的子集,通过 MenuItem 的 setSubMenu 方法 [java] view plaincopyprint? 1. Menu menu1 = new Menu(); 2. menu1.add(new MenuItem("Option 1",Resources.ICONS.page())); 3. menu1.add(new MenuItem("Option 2",Resources.ICONS.page())); 4. Menu menu2 = new Menu(); 5. menu2.add(new MenuItem("Option 4")); 6. menu2.add(new MenuItem("Option 5")); 7. menu2.add(new MenuItem("Option 6")); 8. MenuItem miOption3 = new MenuItem("Option 3"); 9. miOption3.setSubMenu(menu2); 10. menu1.add(miOption3); CheckMenuItem component CheckMenuItem 继承了 MenuItem,其功能是可以让 menu 的 items 被选中。  通过 CheckMenuItem 生成的 Menu,代码如下: [java] view plaincopyprint? 1. Menu menu = new Menu(); 2. menu.add(new CheckMenuItem("Option 1")); 3. menu.add(new CheckMenuItem("Option 2")); 4. menu.add(new CheckMenuItem("Option 3")); 5. MenuBar menuBar = new MenuBar(); 6. menuBar.add(new MenuBarItem("Menu", menu));  当然对于多选组件来说,当然支持分组。CheckMenuItem 可以通过 setGroup 方法进行分组,但是分组后的 CheckMenuItem,则编程单选组件了。 MenuEvent 某个 MenuItem 被选中时,就会触发 MenuEvent 事件。就像一个 Button 被选中的时候,会 触发 ButtonEvent。 MenuItem 可以添加 SelectionListener 监听选中事件。 [java] view plaincopyprint? 1. MenuItem menuItem = new MenuItem("Option1"); 2. menuItem.addSelectionListener(new SelectionListener(){ 3. @Override 4. public void componentSelected(MenuEvent ce) { 5. //Action goes here 6. } 7. }); 目前为止,在我们 RSSReader 项目里面,RssNavigationPanel 组件里添加了两个 buttons——Create feed 和 Link feed。接下来我们要使用 MenuBar 替换他们,让这两个 buttons 呈现在一组菜单里面。因此我们期待的页面效果如下:  修改后的 RssNavigationPanel 类构造函数如下: [java] view plaincopyprint? 1. public RssNavigationPanel() { 2. setHeading("Navigation"); 3. setLayout(new FitLayout()); 4. 5. Menu menu = new Menu(); 6. //Create feed 7. final MenuItem miCreateFeed = new MenuItem("Create feed"); 8. miCreateFeed.setIconStyle("create-feed"); 9. ToolTipConfig createNewToolTipConfig = new ToolTipConfig(); 10. createNewToolTipConfig.setTitle("Create a new RSS feed"); 11. createNewToolTipConfig 12. .setText("Creates a new RSS feed"); 13. miCreateFeed.setToolTip(createNewToolTipConfig); 14. miCreateFeed.addSelectionListener(new 15. SelectionListener() { 16. @Override 17. public void componentSelected(MenuEvent me) { 18. createNewFeedWindow(); 19. } 20. }); 21. menu.add(miCreateFeed); 22. 23. //Link feed 24. final MenuItem miLinkFeed = new MenuItem("Link feed"); 25. miLinkFeed.setIconStyle("link-feed"); 26. ToolTipConfig linkFeedToolTipConfig = new ToolTipConfig(); 27. linkFeedToolTipConfig.setTitle("Link to existing RSS feed"); 28. linkFeedToolTipConfig 29. .setText("Allows you to enter the URL of an existing RSS feed you would like to link to"); 30. miLinkFeed.setToolTip(linkFeedToolTipConfig); 31. final LinkFeedPopup addFeedPopup = new LinkFeedPopup(); 32. addFeedPopup.setConstrainViewport(true); 33. miLinkFeed.addSelectionListener(new SelectionListener() 34. { 35. @Override 36. public void componentSelected(MenuEvent me) { 37. addFeedPopup.show(miLinkFeed.getElement(), "tl-bl?"); 38. } 39. }); 40. menu.add(miLinkFeed); 41. 42. //menuBar 43. MenuBar menuBar = new MenuBar(); 44. MenuBarItem menuBarItem = new MenuBarItem("Add feed", menu); 45. menuBar.add(menuBarItem); 46. setTopComponent(menuBar); 47. 48. add(new FeedList()); 49. } GXT 之旅:第五章:高级 Components(5)——ToolBar 分类: ExtGWT2012-02-07 14:48378 人阅读评论(0)收藏举报 ToolBar component ToolBar component 不是我们传统意义上的 buttons 或 menus。 先前我们 RSSReader 项目,RssNavigationPanel 里面的 buttons 已经被替换成 menus, 接下来我们要是 ToolBar 展示更丰富的功能。ToolBar 里不单单可以添加 butons,而且甚至 ComboBox 或者 Label 都可以添加进来。 一个 ContentPanel 为 toolbar 提供了顶部或者底部的占位符。为了继续整理 RSSReader 项目里 Create feed 和 Link feed 两个 buttons,我们准备添加一个 ToolBar,将他们整合起 来。  在 RssNavigatorPanel 类里,新建一个方法 initToolbar [java] view plaincopyprint? 1. private void initToolbar() {  在方法的内,首先加入 ToolBar 的定义 [java] view plaincopyprint? 1. final ToolBar toolbar = new ToolBar();  新建一个 button——Add feed,用于被添加到 toolbar 里。 [java] view plaincopyprint? 1. final Button btnAddFeed = new Button("Add feed"); 2. btnAddFeed.setIconStyle("create-feed"); 3. ToolTipConfig addFeedToolTipConfig = new ToolTipConfig(); 4. addFeedToolTipConfig.setTitle("Add a new RSS feed"); 5. addFeedToolTipConfig.setText("Adds a new RSS feed"); 6. btnAddFeed.setToolTip(addFeedToolTipConfig);  新建一个 Menu [java] view plaincopyprint? 1. Menu menu = new Menu();  将 Create feed button 定义之后,添加到 menu 里 [java] view plaincopyprint? 1. final MenuItem miCreateFeed = new MenuItem("Create feed"); 2. miCreateFeed.setIconStyle("create-feed"); 3. ToolTipConfig createNewToolTipConfig = new ToolTipConfig(); 4. createNewToolTipConfig.setTitle("Create a new RSS feed"); 5. createNewToolTipConfig.setText("Creates a new RSS feed"); 6. miCreateFeed.setToolTip(createNewToolTipConfig); 7. miCreateFeed.addSelectionListener(new SelectionListener() { 8. @Override 9. public void componentSelected(MenuEvent me) { 10. createNewFeedWindow(); 11. } 12. }); 13. menu.add(miCreateFeed);  同样的将 Link feed button 定义之后,添加到 menu 里 [java] view plaincopyprint? 1. final MenuItem miLinkFeed = new MenuItem("Link feed"); 2. miLinkFeed.setIconStyle("link-feed"); 3. ToolTipConfig linkFeedToolTipConfig = new ToolTipConfig(); 4. linkFeedToolTipConfig.setTitle("Link to existing RSS feed"); 5. linkFeedToolTipConfig 6. .setText("Allows you to enter the URL of an existing RSS feed you would like to link to"); 7. miLinkFeed.setToolTip(linkFeedToolTipConfig); 8. final LinkFeedPopup addFeedPopup = new LinkFeedPopup(); 9. addFeedPopup.setConstrainViewport(true); 10. miLinkFeed.addSelectionListener(new SelectionListener() { 11. @Override 12. public void componentSelected(MenuEvent me) { 13. addFeedPopup.show(miLinkFeed.getElement(), "tl-bl?"); 14. } 15. }); 16. menu.add(miLinkFeed);  层层添加,最后使之能显示出来。 [java] view plaincopyprint? 1. //set menu into button 2. btnAddFeed.setMenu(menu); 3. //set button into tollbar 4. toolbar.add(btnAddFeed); 5. //set toolbar into ContentPanel 6. setTopComponent(toolbar);  完整的 RssNavigationPanel 类,如下: [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.components; 2. 3. import com.danielvaughan.rssreader.client.RSSReaderConstants; 4. import com.danielvaughan.rssreader.client.lists.FeedList; 5. import com.danielvaughan.rssreader.client.services.FeedServiceAsync; 6. import com.danielvaughan.rssreader.client.windows.FeedWindow; 7. import com.danielvaughan.rssreader.shared.model.Feed; 8. import com.extjs.gxt.ui.client.Registry; 9. import com.extjs.gxt.ui.client.event.MenuEvent; 10. import com.extjs.gxt.ui.client.event.SelectionListener; 11. import com.extjs.gxt.ui.client.widget.ContentPanel; 12. import com.extjs.gxt.ui.client.widget.Info; 13. import com.extjs.gxt.ui.client.widget.Window; 14. import com.extjs.gxt.ui.client.widget.button.Button; 15. import com.extjs.gxt.ui.client.widget.layout.FitLayout; 16. import com.extjs.gxt.ui.client.widget.menu.Menu; 17. import com.extjs.gxt.ui.client.widget.menu.MenuItem; 18. import com.extjs.gxt.ui.client.widget.tips.ToolTipConfig; 19. import com.extjs.gxt.ui.client.widget.toolbar.ToolBar; 20. import com.google.gwt.user.client.Element; 21. import com.google.gwt.user.client.rpc.AsyncCallback; 22. 23. public class RssNavigationPanel extends ContentPanel { 24. public RssNavigationPanel() { 25. setHeading("Navigation"); 26. setLayout(new FitLayout()); 27. initToolbar(); 28. add(new FeedList()); 29. } 30. 31. @Override 32. protected void onRender(Element parent, int pos) { 33. super.onRender(parent, pos); 34. 35. 36. } 37. 38. private void createNewFeedWindow() { 39. final FeedServiceAsync feedService = Registry 40. .get(RSSReaderConstants.FEED_SERVICE); 41. feedService.createNewFeed(new AsyncCallback() { 42. @Override 43. public void onFailure(Throwable caught) { 44. Info.display("RSSReader", "Unable to create a new feed"); 45. } 46. 47. @Override 48. public void onSuccess(Feed feed) { 49. final Window newFeedWindow = new FeedWindow(feed); 50. newFeedWindow.show(); 51. } 52. }); 53. } 54. 55. private void initToolbar() { 56. final ToolBar toolbar = new ToolBar(); 57. 58. //btnAddFeed 59. final Button btnAddFeed = new Button("Add feed"); 60. btnAddFeed.setIconStyle("create-feed"); 61. ToolTipConfig addFeedToolTipConfig = new ToolTipConfig(); 62. addFeedToolTipConfig.setTitle("Add a new RSS feed"); 63. addFeedToolTipConfig.setText("Adds a new RSS feed"); 64. btnAddFeed.setToolTip(addFeedToolTipConfig); 65. //miCreateFeed 66. Menu menu = new Menu(); 67. final MenuItem miCreateFeed = new MenuItem("Create feed"); 68. miCreateFeed.setIconStyle("create-feed"); 69. ToolTipConfig createNewToolTipConfig = new ToolTipConfig(); 70. createNewToolTipConfig.setTitle("Create a new RSS feed"); 71. createNewToolTipConfig.setText("Creates a new RSS feed"); 72. miCreateFeed.setToolTip(createNewToolTipConfig); 73. miCreateFeed.addSelectionListener(new SelectionListener() { 74. @Override 75. public void componentSelected(MenuEvent me) { 76. createNewFeedWindow(); 77. } 78. }); 79. menu.add(miCreateFeed); 80. //miLinkFeed 81. final MenuItem miLinkFeed = new MenuItem("Link feed"); 82. miLinkFeed.setIconStyle("link-feed"); 83. ToolTipConfig linkFeedToolTipConfig = new ToolTipConfig(); 84. linkFeedToolTipConfig.setTitle("Link to existing RSS feed"); 85. linkFeedToolTipConfig 86. .setText("Allows you to enter the URL of an existing RSS feed you would like to link to"); 87. miLinkFeed.setToolTip(linkFeedToolTipConfig); 88. final LinkFeedPopup addFeedPopup = new LinkFeedPopup(); 89. addFeedPopup.setConstrainViewport(true); 90. miLinkFeed.addSelectionListener(new SelectionListener() { 91. @Override 92. public void componentSelected(MenuEvent me) { 93. addFeedPopup.show(miLinkFeed.getElement(), "tl-bl?"); 94. } 95. }); 96. menu.add(miLinkFeed); 97. //set menu into button 98. btnAddFeed.setMenu(menu); 99. //set button into tollbar 100. toolbar.add(btnAddFeed); 101. //set toolbar into ContentPanel 102. setTopComponent(toolbar); 103. } 104. }  运行效果入下: GXT 之旅:第五章:高级 Components(6)——TabPanel, Status 大概介绍 分类: ExtGWT2012-02-07 15:48444 人阅读评论(0)收藏举报 TabPanel TabPanel 继承 Container,作为可以容器,可以负责显示和管理 TabItem 对象集。TabItem 对象集可以方法被 add 或 remove。每一个 TabItem 都有一个 id,用来通过 findItem 方法来 获得其对象。通过 setSelectedItem 方法可是设置一个 TabItem 呈现被选中状态。通过 getSelectedItem 方法可以获得当前没选中的 TabItem。 TabItem TabItem 继承 LayoutContainer。当一个TabItem 被添加到一个TabPanel时,可以被closed, disabled,同时在头部支持显示一个 icon。 Status component Status 一般和 ToolBar 结合使用,用来作为一个状态栏,这种功能很类似一些桌面程序。 接下来,会在 RSSReader 项目里,初步的加入涉及到的功能,但是后面的章节会详细介绍。  在 RssMainPanel 在构造函数里面,回滚到添加 ItemGrid 的时候。 [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.components; 2. 3. import com.danielvaughan.rssreader.client.grids.ItemGrid; 4. import com.extjs.gxt.ui.client.widget.ContentPanel; 5. import com.extjs.gxt.ui.client.widget.layout.FitLayout; 6. 7. public class RssMainPanel extends ContentPanel { 8. public RssMainPanel() { 9. setHeading("Main"); 10. setLayout(new FitLayout()); 11. add(new ItemGrid()); 12. // add(new ItemCategoryGrid()); 13. // add(new ItemPagingGrid()); 14. } 15. }  在 RssMainPanel 构造函数的尾端,新建一个 ToolBar [java] view plaincopyprint? 1. ToolBar toolBar = new ToolBar();  紧接着,新建一个 Status [java] view plaincopyprint? 1. Status status = new Status(); 2. status.setWidth(150);  使用 Status.setBox 方法设置其显示的边框状态,然后初始化其状态的内容是 OK [java] view plaincopyprint? 1. status.setBox(true); 2. status.setText("OK");  将 status 放入到 toolBar 里面,将 toolBar 放入到 ContentPanel 底部 [java] view plaincopyprint? 1. toolBar.add(status); 2. setBottomComponent(toolBar); GXT 之旅:第六章:Templates(1)——Template(1) 分类: ExtGWT2012-02-07 17:39469 人阅读评论(0)收藏举报 第六章:Templates 本章我们要了解 Templates,以及学习他们是如何方便我们去自定义数据的格式化和显示。 我们也会详细了解 XTemplates 的丰富功能 本章,我们会涉及到如下 GXt 功能集  Template  XTemplate  RowExpander  ListView  ModelProcessor  CheckBoxListView 之前的章节,我们学习了 data-backed components 使用 ModelData objects 如何自动的 加载数据的。我们是通过指定其使用 ModelData 里面的某一列,作为显示内容。但是如果 我们希望显示的内容不仅仅是一列,我们要怎么办?比如,如果我们有一个 ModelData 对 象,里面有两列内容——first name 和 last name,但是我们希望显示的时候是 full name(first name+last name)。 当然 GXT 会想到这样的问题,并且提供了连个解决方案。第一:ModelProcessor 可以预加 工 ModelData,定义出一个新的列(以后会讲到 )。第二种就是使用 Template。 首先,我们要做一些准备工作,在 Feed 和 Item 类里面加入更多的 fields,并在后端的 server 方法里面灌入数据——以供给 Template 使用。  Feed 类中,加入两个新的 fields:imageUrl,存储图片的 url;items 用来存 储 Item 集合。当然别忘了 setter 和 getter 方法 [java] view plaincopyprint? 1. private String imageUrl; 2. private List items = new ArrayList();  Item 类加入 publication 和 thumbnailUrl。因为是直接继承的 BaseModel,所 以注意 getter 和 setter 方法的书写: [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.shared.model; 2. 3. import java.util.Date; 4. 5. import com.extjs.gxt.ui.client.data.BaseModel; 6. 7. @SuppressWarnings("serial") 8. public class Item extends BaseModel { 9. 10. public Item() { 11. 12. } 13. 14. public String getCategory() { 15. return get("category"); 16. } 17. 18. public String getDescription() { 19. return get("description"); 20. } 21. 22. public String getLink() { 23. return get("link"); 24. } 25. 26. public Date getPubDate() { 27. return get("pubDate"); 28. } 29. 30. public String getThumbnailUrl() { 31. return get("thumbnailUrl"); 32. } 33. 34. public String getTitle() { 35. return get("title"); 36. } 37. 38. public void setCategory(String category) { 39. set("category", category); 40. } 41. 42. public void setDescription(String description) { 43. set("description", description); 44. } 45. 46. public void setLink(String link) { 47. set("link", link); 48. } 49. 50. public void setPubDate(Date pubDate) { 51. set("pubDate", pubDate); 52. } 53. 54. public void setThumbnailUrl(String thumbnailUrl) { 55. set("thumbnailUrl", thumbnailUrl); 56. } 57. 58. public void setTitle(String title) { 59. set("title", title); 60. } 61. }  FeedService 接口里修改原来无参数的 loadFeedList()方法,为 [java] view plaincopyprint? 1. List loadFeedList(boolean loadItems);  别忘了在 FeedServiceAsync 修改对应的回调方法 [java] view plaincopyprint? 1. void loadFeedList(boolean loadItems, AsyncCallback> 2. callback);  FeedServiceImpl 类里,通过修改 loadFeedList()为 loadFeedList(boolean loadItems),实现其抽象方法。 [java] view plaincopyprint? 1. @Override 2. public List loadFeedList(boolean loadItems)) { 3. feeds.clear(); 4. Set feedUrls = persistence.loadFeedList(); 5. for (String feedUrl : feedUrls) { 6. feeds.put(feedUrl, loadFeed(feedUrl,loadItems))); 7. } 8. return new ArrayList(feeds.values()); 9. }  修改 FeedServiceImpl.loadFeed 方法,加入新的 loadItems 参数。具体实现 如下: [java] view plaincopyprint? 1. private Feed loadFeed(String feedUrl, boolean loadItems) { 2. Feed feed = new Feed(feedUrl); 3. try { 4. SAXBuilder parser = new SAXBuilder(); 5. Document document = parser.build(new URL(feedUrl)); 6. Element eleRoot = document.getRootElement(); 7. Element eleChannel = eleRoot.getChild("channel"); 8. feed.setTitle(eleChannel.getChildText("title")); 9. feed.setDescription(eleChannel.getChildText("description")); 10. feed.setLink(eleChannel.getChildText("link")); 11. Element eleImage = eleChannel.getChild("image"); 12. feed.setImageUrl(""); 13. if (eleImage != null) { 14. Element eleUrl = eleImage.getChild("url"); 15. if (eleUrl != null) { 16. feed.setImageUrl(eleUrl.getText()); 17. } 18. } 19. if (loadItems) { 20. feed.setItems(loadItems(feedUrl)); 21. } 22. return feed; 23. } catch (IOException e) { 24. LOGGER.log(Level.SEVERE, "IO Error loading feed", e); 25. return feed; 26. } catch (JDOMException e) { 27. LOGGER.log(Level.SEVERE, "Error parsing feed", e); 28. return feed; 29. } 30. }  牵扯到引用 loadFeed 方法的相关方法修改。 [java] view plaincopyprint? 1. @Override 2. public void addExistingFeed(String feedUrl) { 3. Feed loadResult = loadFeed(feedUrl, false); 4. if (loadResult.getTitle() != null) { 5. feeds.put(feedUrl, loadResult); 6. persistence.saveFeedList(feeds.keySet()); 7. } 8. }  相应的 loadItems 方法也要修改, [java] view plaincopyprint? 1. @Override 2. @SuppressWarnings("unchecked") 3. public List loadItems(String feedUrl) { 4. List items = new ArrayList(); 5. try { 6. SAXBuilder parser = new SAXBuilder(); 7. Document document = parser.build(new URL(feedUrl)); 8. Element eleRoot = document.getRootElement(); 9. Element eleChannel = eleRoot.getChild("channel"); 10. List itemElements = eleChannel.getChildren("item"); 11. for (Element eleItem : itemElements) { 12. Item item = new Item(); 13. item.setTitle(eleItem.getChildText("title")); 14. item.setDescription(eleItem.getChildText("description")); 15. item.setLink(eleItem.getChildText("link")); 16. item.setCategory(eleItem.getChildText("category")); 17. Namespace ns = Namespace.getNamespace("media", 18. "http://search.yahoo.com/mrss/"); 19. Element eleThumbnail = eleItem.getChild("thumbnail", ns); 20. if (eleThumbnail != null) { 21. item.setThumbnailUrl(eleThumbnail.getAttributeValue("url")); 22. } 23. String pubDateStr = eleItem.getChildText("pubDate"); 24. if (pubDateStr != null) { 25. try { 26. DateFormat df = new SimpleDateFormat( 27. "EEE', 'dd' 'MMM' 'yyyy' 'HH:mm:ss' 'Z"); 28. item.setPubDate(df.parse(pubDateStr)); 29. } catch (ParseException e) { 30. item.setPubDate(null); 31. } 32. } 33. items.add(item); 34. } 35. return items; 36. } catch (IOException e) { 37. e.printStackTrace(); 38. return items; 39. 40. } catch (JDOMException e) { 41. e.printStackTrace(); 42. return items; 43. } 44. }  最后,在 FeedList 的 onRender 方法里,修改为新的 service 方调用 [java] view plaincopyprint? 1. protected void load(Object loadConfig, AsyncCallback> 2. callback) { 3. feedService.loadFeedList(false, callback); 4. }  目前为止,准备工作已经完成,下一章会在此数据结构的基础之上使用 Template  修改后完整的 FeedServiceImpl 类如下: [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.server.services; 2. 3. import java.io.IOException; 4. import java.net.URL; 5. import java.text.DateFormat; 6. import java.text.ParseException; 7. import java.text.SimpleDateFormat; 8. import java.util.ArrayList; 9. import java.util.List; 10. import java.util.Map; 11. import java.util.Set; 12. import java.util.UUID; 13. import java.util.logging.Level; 14. import java.util.logging.Logger; 15. 16. import org.jdom.Attribute; 17. import org.jdom.Document; 18. import org.jdom.Element; 19. import org.jdom.JDOMException; 20. import org.jdom.Namespace; 21. import org.jdom.input.SAXBuilder; 22. 23. import com.danielvaughan.rssreader.client.services.FeedService; 24. import com.danielvaughan.rssreader.server.utils.FilePersistence; 25. import com.danielvaughan.rssreader.server.utils.Persistence; 26. import com.danielvaughan.rssreader.shared.model.Category; 27. import com.danielvaughan.rssreader.shared.model.Feed; 28. import com.danielvaughan.rssreader.shared.model.Item; 29. import com.extjs.gxt.ui.client.data.BasePagingLoadResult; 30. import com.extjs.gxt.ui.client.data.ModelData; 31. import com.extjs.gxt.ui.client.data.PagingLoadConfig; 32. import com.extjs.gxt.ui.client.data.PagingLoadResult; 33. import com.google.gwt.dev.util.collect.HashMap; 34. import com.google.gwt.user.server.rpc.RemoteServiceServlet; 35. 36. @SuppressWarnings("serial") 37. public class FeedServiceImpl extends RemoteServiceServlet implements 38. FeedService { 39. 40. private final static Logger LOGGER = Logger.getLogger(FeedServiceImpl.class 41. .getName()); 42. 43. private Map feeds = new HashMap(); 44. 45. private final Persistence persistence = new FilePersistence(); 46. 47. @Override 48. public void addExistingFeed(String feedUrl) { 49. Feed loadResult = loadFeed(feedUrl, false); 50. if (loadResult.getTitle() != null) { 51. feeds.put(feedUrl, loadResult); 52. persistence.saveFeedList(feeds.keySet()); 53. } 54. } 55. 56. @Override 57. public Feed createNewFeed() { 58. UUID uuid = UUID.randomUUID(); 59. return new Feed(uuid.toString()); 60. } 61. 62. private PagingLoadResult getPagingLoadResult(List items, 63. PagingLoadConfig config) { 64. List pageItems = new ArrayList(); 65. int offset = config.getOffset(); 66. int limit = items.size(); 67. if (config.getLimit() > 0) { 68. limit = Math.min(offset + config.getLimit(), limit); 69. } 70. for (int i = config.getOffset(); i < limit; i++) { 71. pageItems.add(items.get(i)); 72. } 73. return new BasePagingLoadResult(pageItems, offset, items.size()); 74. 75. } 76. 77. @Override 78. public List loadCategorisedItems(String feedUrl, 79. Category category) { 80. List items = loadItems(feedUrl); 81. Map> categorisedItems = new HashMap>(); 82. for (Item item : items) { 83. String itemCategoryStr = item.getCategory(); 84. if (itemCategoryStr == null) { 85. itemCategoryStr = "Uncategorised"; 86. } 87. List categoryItems = categorisedItems.get(itemCategoryStr); 88. if (categoryItems == null) { 89. categoryItems = new ArrayList(); 90. } 91. categoryItems.add(item); 92. categorisedItems.put(itemCategoryStr, categoryItems); 93. } 94. if (category == null) { 95. List categoryList = new ArrayList(); 96. for (String key : categorisedItems.keySet()) { 97. categoryList.add(new Category(key)); 98. } 99. return categoryList; 100. } else { 101. return new ArrayList(categorisedItems.get(category 102. .getTitle())); 103. } 104. } 105. 106. private Feed loadFeed(String feedUrl, boolean loadItems) { 107. Feed feed = new Feed(feedUrl); 108. try { 109. SAXBuilder parser = new SAXBuilder(); 110. Document document = parser.build(new URL(feedUrl)); 111. Element eleRoot = document.getRootElement(); 112. Element eleChannel = eleRoot.getChild("channel"); 113. feed.setTitle(eleChannel.getChildText("title")); 114. feed.setDescription(eleChannel.getChildText("description")); 115. feed.setLink(eleChannel.getChildText("link")); 116. Element eleImage = eleChannel.getChild("image"); 117. feed.setImageUrl(""); 118. if (eleImage != null) { 119. Element eleUrl = eleImage.getChild("url"); 120. if (eleUrl != null) { 121. feed.setImageUrl(eleUrl.getText()); 122. } 123. } 124. if (loadItems) { 125. feed.setItems(loadItems(feedUrl)); 126. } 127. return feed; 128. } catch (IOException e) { 129. LOGGER.log(Level.SEVERE, "IO Error loading feed", e); 130. return feed; 131. } catch (JDOMException e) { 132. LOGGER.log(Level.SEVERE, "Error parsing feed", e); 133. return feed; 134. } 135. } 136. 137. @Override 138. public List loadFeedList(boolean loadItems) { 139. feeds.clear(); 140. Set feedUrls = persistence.loadFeedList(); 141. for (String feedUrl : feedUrls) { 142. feeds.put(feedUrl, loadFeed(feedUrl, loadItems)); 143. } 144. return new ArrayList(feeds.values()); 145. } 146. 147. @Override 148. @SuppressWarnings("unchecked") 149. public List loadItems(String feedUrl) { 150. List items = new ArrayList(); 151. try { 152. SAXBuilder parser = new SAXBuilder(); 153. Document document = parser.build(new URL(feedUrl)); 154. Element eleRoot = document.getRootElement(); 155. Element eleChannel = eleRoot.getChild("channel"); 156. List itemElements = eleChannel.getChildren("item"); 157. for (Element eleItem : itemElements) { 158. Item item = new Item(); 159. item.setTitle(eleItem.getChildText("title")); 160. item.setDescription(eleItem.getChildText("description")); 161. item.setLink(eleItem.getChildText("link")); 162. item.setCategory(eleItem.getChildText("category")); 163. Namespace ns = Namespace.getNamespace("media", 164. "http://search.yahoo.com/mrss/"); 165. Element eleThumbnail = eleItem.getChild("thumbnail", ns); 166. if (eleThumbnail != null) { 167. item.setThumbnailUrl(eleThumbnail.getAttributeValue("url")); 168. } 169. String pubDateStr = eleItem.getChildText("pubDate"); 170. if (pubDateStr != null) { 171. try { 172. DateFormat df = new SimpleDateFormat( 173. "EEE', 'dd' 'MMM' 'yyyy' 'HH:mm:ss' 'Z"); 174. item.setPubDate(df.parse(pubDateStr)); 175. } catch (ParseException e) { 176. item.setPubDate(null); 177. } 178. } 179. items.add(item); 180. } 181. return items; 182. } catch (IOException e) { 183. e.printStackTrace(); 184. return items; 185. 186. } catch (JDOMException e) { 187. e.printStackTrace(); 188. return items; 189. } 190. } 191. 192. @Override 193. public PagingLoadResult loadItems(String feedUrl, 194. PagingLoadConfig config) { 195. List items = loadItems(feedUrl); 196. return getPagingLoadResult(items, config); 197. } 198. 199. @Override 200. public void saveFeed(Feed feed) { 201. Element eleRoot = new Element("rss"); 202. eleRoot.setAttribute(new Attribute("version", "2.0")); 203. 204. // Create a document from the feed object 205. Document document = new Document(eleRoot); 206. 207. Element eleChannel = new Element("channel"); 208. Element eleTitle = new Element("title"); 209. Element eleDescription = new Element("description"); 210. Element eleLink = new Element("link"); 211. 212. eleTitle.setText(feed.getTitle()); 213. eleDescription.setText(feed.getDescription()); 214. eleLink.setText(feed.getLink()); 215. 216. eleChannel.addContent(eleTitle); 217. eleChannel.addContent(eleDescription); 218. eleChannel.addContent(eleLink); 219. 220. eleRoot.addContent(eleChannel); 221. 222. persistence.saveFeedXml(feed.getUuid(), document); 223. addExistingFeed(persistence.getUrl(feed.getUuid())); 224. } 225. } GXT 之旅:第六章:Templates(1)——Template(2) 分类: ExtGWT2012-02-09 16:29484 人阅读评论(0)收藏举报 Template 通过使用 Template 类,可以定义一组 html 的字符串去渲染 ModelData 或者 Params。其 生成的 html 字符串里面可以设置占位符,要来作为变量可以设置参数。 一个占位符参数的定义,需要通过{}给包裹起来。一个使用 firstName 和 lastName 定义的 Template 如下: [java] view plaincopyprint? 1. Template template = new Template("My full name is {firstName} {lastName}."); 接下来,我们需要 Params 给占位符参数设值 [java] view plaincopyprint? 1. Params data = new Params(); 2. data.set("firstName", "Daniel"); 3. data.set("lastName", "Vaughan"); 最后,让 template 应用 data(Params)的值域,需要通过 applyTemplate 方法 [java] view plaincopyprint? 1. template.applyTemplate(data); Template 可以被预编译,这样就可以节约头期使用正规表达式的时间。实现此功能需要调 用 Template.compile 方法。 在 RSSReader 项目里,我们要自定义组件——ItemPanel 用来使用 Template 将一个对象 渲染成 html。  在 com.danielvaughan.rssreader.client.components 包下,新建 ItemPanel extends ContentPanel [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.components; 2. 3. import com.extjs.gxt.ui.client.widget.ContentPanel; 4. 5. public class ItemPanel extends ContentPanel { 6. 7. }  新建一个属性——GWT 的 HTML widget [java] view plaincopyprint? 1. private final HTML html = new HTML();  当然要 override onRender 方法——设置标题,并将 HTMl 加入到 ContentPanel 里 [java] view plaincopyprint? 1. @Override 2. protected void onRender(Element parent, int index) { 3. super.onRender(parent, index); 4. setHeading("Item"); 5. add(html); 6. }  继续在 onRender 方法里——因为我们希望让 ContentPanel 能够自适应 HTML widget 的大小,所以要设置成 FitLayout 布局方式。当然我们同样的希 望设置 HTML widget 应用一个 CSS style,这样我们就可以编辑其样式,来 设置 HTML 的显示方式: [java] view plaincopyprint? 1. @Override 2. protected void onRender(Element parent, int index) { 3. super.onRender(parent, index); 4. setHeading("Item"); 5. setLayout(new FitLayout()); 6. html.setStylePrimaryName("item"); 7. add(html); 8. }  接下来,我们构造 template 的字符串——Template 接收的是一个 String 类 型的字符串,我们可以使用 java 标准的 StringBuilder 类来生成 String。我们 将占位符参数通过大括号包裹起来。 [java] view plaincopyprint? 1. private String getTemplate() { 2. StringBuilder sb = new StringBuilder(); 3. sb.append("

{title}

"); 4. sb.append("

{pubDate}

"); 5. sb.append("
"); 6. sb.append(""); 7. sb.append("

{description}

"); 8. return sb.toString(); 9. }  新建一个方法 displayItem——它会有一个参数为 Item。将 Item 对象里面属 性存储的值,赋值给 template 的占位符,根据 item 的属性名称和 template 里占位符的属性名称一一对应。此操作 GXT 已经给我们提供了 Util 类,具体 实现如下: [java] view plaincopyprint? 1. public void displayItem(Item item) { 2. 3. setHeading(item.getTitle()); 4. Template template = new Template(getTemplate()); 5. html.setHTML(template.applyTemplate(Util.getJsObject(item, 1))); 6. }  下面在程序的入口 html 文件里(RSSReader.html),引用新建的 css 文件 (css/item.css) [java] view plaincopyprint? 1.  在 item.css 样式文件里,定义相关其 template 设置的 html 标记的样式。 [css] view plaincopyprint? 1. .item h1 { 2. font-size: 1.5em; 3. } 4. 5. .item img { 6. border: 1px solid #000; 7. float: left; 8. margin-right: 10px; 9. } 10. 11. .item hr { 12. border-bottom: 1px solid #000; 13. }  我们现在需要新建一个测试类去实验,让 ItemPanel 显示 item 对象的 template。在 com.danielvaughan.rssreader.client 包下,新建 TestObjects 类,具体实现如下: [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client; 2. 3. import java.util.Date; 4. 5. import com.danielvaughan.rssreader.shared.model.Item; 6. 7. public class TestObjects { 8. public static Item getTestItem() { 9. Item testItem = new Item(); 10. testItem.setTitle("Computers get more powerful"); 11. testItem 12. .setDescription("New computers are more powerful " 13. + "than the computers that were around a year ago. " 14. + "They are also much more powerful than the computers from five years ago. " 15. + "If you were to compare current computers with the computers of twenty " 16. + "years ago you would fine they are far more powerful."); 17. testItem.setLink("http://www.example.com/item573.html"); 18. testItem.setPubDate(new Date()); 19. testItem.setCategory("Category"); 20. testItem 21. .setThumbnailUrl("http://localhost:8888/computers.jpg"); 22. return testItem; 23. } 24. }  在 RSSReader 类里,将 ItemPanel 替换 RssMainPanel [java] view plaincopyprint? 1. //RssMainPanel mainPanel = new RssMainPanel(); 2. ItemPanel mainPanel = new ItemPanel(); 3. mainPanel.displayItem(TestObjects.getTestItem());  显示效果如下: 在其他 components 里使用 Templates 不单单 HTML widget 可以使用 Template,其他的 components 也可以使用 Template 去定 义 HTML。具体来说,ListField,ComboBox,ToolTipConfig 都可以使用 Template。 当我们使用 ListField 或 ComboBox 的时候,Template 的使用是去定义其里面的列表 item。 例如,使用 Template 定义多个 fields 的组合显示,去替换 ListField 里面的单一的 field 显 示。但是要注意,因为 ListField 或 ComboBox 里面都是使用 ModelData 的结果集,因此需 要针对每一个 item 设置其 Template。 Template 有一个专门的标签,此标记提供一个方法去迭代 list 组件里面每一个 item 的, 并赋予其 template。在了解 XTemplate 之前,我们会详细了解标记的使用。 随着时间的推移,我们会不断的修改 RSSReader 项目里面 FeedList 类,让其显示 Feed 对 象里name和部分description内容,取代之前仅仅是显示name字段。虽然我们使用ListField 举例,但是其操作过程同样应用与 ComboBox component。  我们在 FeedList 类里面,定义一个 getTemplate 方法,用来返回 Template 形式的字符串  在此方法里,我们需要使用用于加工 store 里面的每个 ModelData。 [java] view plaincopyprint? 1. private String getTemplate() { 2. StringBuilder sb = new StringBuilder(); 3. sb.append(""); 4. sb.append(""); 5. return sb.toString(); 6. }  在标记之间,我们使用
标签来包裹这每一行的显示效果,其 css style 设置成“x-combo-list-item” [java] view plaincopyprint? 1. private String getTemplate() { 2. StringBuilder sb = new StringBuilder(); 3. sb.append(""); 4. sb.append("
{title} -{description}
"); 5. sb.append("
"); 6. return sb.toString(); 7. }  ListField 同样提供 setTemplate 方法——在 FeedList 类中,onRender 方法 内,替换 setDisplayField 方法。 [java] view plaincopyprint? 1. //feedList.setDisplayField("title"); 2. feedList.setTemplate(getTemplate());  修改后的整个 FeedList 类如下: [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.lists; 2. 3. import java.util.List; 4. 5. import com.danielvaughan.rssreader.client.RSSReaderConstants; 6. import com.danielvaughan.rssreader.client.services.FeedServiceAsync; 7. import com.danielvaughan.rssreader.shared.model.Feed; 8. import com.extjs.gxt.ui.client.Registry; 9. import com.extjs.gxt.ui.client.data.BaseListLoader; 10. import com.extjs.gxt.ui.client.data.BeanModel; 11. import com.extjs.gxt.ui.client.data.BeanModelReader; 12. import com.extjs.gxt.ui.client.data.ListLoadResult; 13. import com.extjs.gxt.ui.client.data.ListLoader; 14. import com.extjs.gxt.ui.client.data.LoadEvent; 15. import com.extjs.gxt.ui.client.data.RpcProxy; 16. import com.extjs.gxt.ui.client.event.LoadListener; 17. import com.extjs.gxt.ui.client.store.ListStore; 18. import com.extjs.gxt.ui.client.widget.LayoutContainer; 19. import com.extjs.gxt.ui.client.widget.form.ListField; 20. import com.extjs.gxt.ui.client.widget.layout.FitLayout; 21. import com.google.gwt.user.client.Element; 22. import com.google.gwt.user.client.rpc.AsyncCallback; 23. 24. public class FeedList extends LayoutContainer { 25. private final ListStore feedStoreR = Registry 26. .get(RSSReaderConstants.FEED_STORE); 27. 28. public FeedList() { 29. setLayout(new FitLayout()); 30. } 31. 32. private String getTemplate() { 33. StringBuilder sb = new StringBuilder(); 34. sb.append(""); 35. sb.append("
{title} -{description}
"); 36. sb.append("
"); 37. return sb.toString(); 38. } 39. 40. @Override 41. protected void onRender(Element parent, int index) { 42. super.onRender(parent, index); 43. final ListField feedList = new ListField(); 44. // 0:从 Registry 里获得 Service 45. final FeedServiceAsync feedService = (FeedServiceAsync) Registry 46. .get(RSSReaderConstants.FEED_SERVICE); 47. // 1:定义 proxy 在 load 方法里掉用 Serivce 里的方法 48. RpcProxy> proxy = new RpcProxy>() { 49. @Override 50. protected void load(Object loadConfig, 51. AsyncCallback> callback) { 52. feedService.loadFeedList(false, callback); 53. 54. } 55. }; 56. // 2:定义 Reader 57. BeanModelReader reader = new BeanModelReader(); 58. // 3:将 proxy 和 reader 传入,定义 loader 59. ListLoader> loader = new BaseListLoader>( 60. proxy, reader); 61. // 4:传入 loader,生成 store,此时还没有 load 数据 62. final ListStore feedStore = new ListStore(loader); 63. // 5:将 stroe 绑定到 data-backed component 身上 64. feedList.setStore(feedStoreR); 65. //feedList.setDisplayField("title"); 66. feedList.setTemplate(getTemplate()); 67. // 6:真正的 load 数据,load 成功之后,data-backed component 会自动的显示出来。 68. loader.load(); 69. 70. loader.addLoadListener(new LoadListener() { 71. @Override 72. public void loaderLoad(LoadEvent le) { 73. feedStoreR.add(feedStore.getModels()); 74. 75. } 76. }); 77. 78. add(feedList); 79. } 80. }  运行效果如下: GXT 之旅:第六章:Templates(2)——XTemplate(1) 分类:ExtGWT2012-02-23 09:57447 人阅读评论(0)收藏举报 XTemplate XTemplate 比 Template 更为有用,除了拥有 Template 相同的功能之外,还具有更多有用 的功能——提供使用更多的标记来满足自己需要的 html 显示效果。 为了下面例子的引用,首先,定义一个 Person 的 ModalData [java] view plaincopyprint? 1. public class Person extends BaseModel { 2. public Person(String firstName,String lastName) { 3. set("firstName", firstName); 4. set("lastName", lastName); 5. } 6. } For 功能  首先,让我们深入了解一下上一个例子中我们所使用的 for。首先我们先第一 个 friends 的 list [java] view plaincopyprint? 1. List friends = Arrays.asList(new Person("Fred", "Bloggs"), 2. new Person("John", "Smith"));  标签的 for 操作符,可以迭代 friends 里面的每一个 item,并赋予 template。 字符“.”说明了,template 会加工处理 friends 里面的每一个 item [html] view plaincopyprint? 1. 2.

{firstName} {lastName}

3.
 将 friends 应用了如上的 template 的时候,template 会自动的根据占位符变 量名去寻找 friends 的每一个对象里面的值。运行的结果大致如下: Fred Bloggs John Smith  在 friends 的基础之上,我们再新建一个 person 对象,用来存储 friends [java] view plaincopyprint? 1. Person person = new Person("Daniel", "Vaughan"); 2. person.set("friends", friends);  现在我们要加工 person 的内容,并且加工 person 里面存储的 friends 内容的 话,我们的 template 的定义应该如下(此时在看看 for 操作符后面的变化) [java] view plaincopyprint? 1.

{firstName} {lastName}'s friends:

2.
    3. 4.
  • {firstName} {lastName}
  • 5.
    6.
Daniel Vaughan's friends:  Fred Bloggs  John Smith  运行结果如上。  习惯上来说,我们一般自定义一个方法,来设置 template 的 string,自然的 getTemplate()方法命名跟直观易懂一些。 [java] view plaincopyprint? 1. private String getTemplate() { 2. StringBuilder sb = new StringBuilder(); 3. sb.append("

{firstName} {lastName}'s friends

"); 4. sb.append("
    "); 5. sb.append(""); 6. sb.append("
  • {firstName} {lastName}
  • "); 7. sb.append("
    "); 8. sb.append("
"); 9. return sb.toString(); 10. }  template 的创建和数据应用如下 [java] view plaincopyprint? 1. XTemplate xTemplate = XTemplate.create(getTemplate()); 2. String html = template.applyTemplate(Util.getJsObject(person, 2));  注意 Util.getJsObject 方法参数的含义,person 是指应用 template 的属于;2 是指 person 数据挖掘的深度 if 功能 标记,同样有 if 功能的操作符,使用于按条件处理的情况。  让我们在 Person ModelData 里加入 age 字段 [java] view plaincopyprint? 1. public class Person extends BaseModel { 2. public Person(String firstName,String lastName, int age) { 3. set("firstName", firstName); 4. set("lastName", lastName); 5. set("age", age); 6. } 7. }  在此 Person 的结构下,定义一个新的 friends list [java] view plaincopyprint? 1. List friends = Arrays.asList(new Person("Fred", "Bloggs", 2. 20), new Person("John", "Smith", 40)); 3. Person person = new Person("Daniel", "Vaughan", 30); 4. person.set("friends", friends);  我们在定义 template string 的时候,使用 if 操作符,限定 age 大于 30 的 friends 数据才会被 html 渲染出来 [html] view plaincopyprint? 1.

{firstName} {lastName}'s friends over 30:

2.
    3. 4. 5.
  • {firstName} {lastName}
  • 6.
    7.
    8.
 将上面的 friends 和 template 应用在一起之后,显示的内容如下 Daniel Vaughan's friends over 30:  John Smith  注意,if 操作符后面的大于符号使用的是> 下面是一个列表介绍不同的操 作符的 比较关系 操作符 说明 等于 == 如果是 String 类型也支持 大于 > > 小于 < < 不等于 !=  注意:在标记里没有 else 操作符,如果想实现 else 的语句跳转,我们 可以使用 if 的相反条件判断。  if 比较表达式,支持使用 ModelData 的 field。比如之前的比较表达式是年龄 大于 30,我们可以替换为年龄大于 person 对象里面 age 属性的值。我们可 以这样定义 template [html] view plaincopyprint? 1.

{firstName} {lastName}'s friends over {age}:

2.
    3. 4. 5.
  • {firstName} {lastName}
  • 6.
    7.
    8.
 注意,firstName 不可以命名成 first-name。不要使用连字号。 支持简单的数学运算 在 Template 中,支持简单的数学运算。 比如我们希望 age 的属性加 1,那么可以写成{age+1}。 如果我们希望显示的 friends 的 age 要大于 person 的 age 的话,我们可以如下定义 template [java] view plaincopyprint? 1.

{firstName} {lastName}'s friends over {age}:

2.
    3. 4. 5.
  • {firstName} {lastName} ({age}-{parent.age} years older)
  • 6.
    7.
    GXT 之旅:第六章:Templates(2)——XTemplate(2) 分类: ExtGWT2012-03-06 14:33482 人阅读评论(0)收藏举报 XTemplate 应用于其他的 components 除了 ComboBox 和 ListField 之外,还有其他的一些 components 可以使用 XTemplate  RowExpander  ListView  CheckBoxListView  ColorPalette RowExpander 如果希望让 Grid 的某一列的展示具有一定的显示效果的话,我们就得使用 RowExpander。 RowExpander 继承自 ColumnConfig,因此 RowExpander 的使用过程和 ColumnConfig 一 样,定义好之后直接添加到 ColumnModel 里。 当 RowExpander 被添加到 Grid 之后,其显示效果会在列的右上角有一个+的 button。点击 之后内容就会伸展开来,并且变为-的 button 为了让 RowExpander 生效,我们一定要记住——使用 Grid 的 addPlugin()方法。 和 ListField 不同的是,当 Grid 通过 RowExpander 应用 XTemplate 的时候,不需要我们定 义 tpl for 标记,Grid 就会自动的把效果应用到每一行里。 现在,在 RSSReader 项目里,我们就创建一个新版本的 ItemGrid,来使用 RowExpander 显示数据。  打开:com.danielvaughan.rssreader.client.grids.ItemGrid,在 onRender 方 法里,在 Grid 的最后一个列的定义之后,新建一个 XTemplate。这个真实的 template 内容是一个 img 的 html 图片标签,然后 src 属性是 Item 对象里面 的 thumbnailUrl 字段,加上 html 标记 p,显示 item 对象的 description 字段 [javascript] view plaincopyprint? 1. XTemplate xTemplate = XTemplate 2. .create("

    {description}

    ");  然后新建一个 RowExpander 对象,并设置 xTemplate [java] view plaincopyprint? 1. RowExpander rowExpander = new RowExpander(); 2. rowExpander.setTemplate(xTemplate);  将新建好的 RowExpander 加入到 columns 里——ColumnConfig 的配置结果 集 [java] view plaincopyprint? 1. columns.add(rowExpander);  我们还需要将 RowExpander 通过 Grid 的 addPlugin 方法注册到 Grid 中去 [java] view plaincopyprint? 1. grid.addPlugin(rowExpander);  在 com.danielvaughan.rssreader.client.RSSReader 里,删除掉先前章节使 用的 ItemPanel 创建代码,取消掉 RssMainPanel 穿件代码的注释 [java] view plaincopyprint? 1. RssMainPanel mainPanel = new RssMainPanel(); 2. //remove ItemPanel mainPanel = new ItemPanel(); 3. //remove mainPanel.displayItem(TestObjects.getTestItem());  运行之后,效果如下: ListView ListView 支持通过 XTemplate 自定制数据集合的显示。这是一个很灵活的 component,来 让我们可以具体的控制数据的显示效果。比如,icons,grid,list 或者其他的 components 都可以通过 XTemplate 和 CSS 联合起来定制其显示效果。 为了证明 ListView 是如何工作的,在 RSSReader 项目里,我们准备新建一个 ListView 去 展示 Feed objects 对象集合。  新建类: [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.lists; 2. 3. import com.extjs.gxt.ui.client.widget.LayoutContainer; 4. 5. public class FeedOverviewView extends LayoutContainer { 6. 7. }  定义一个 listView 的 component [java] view plaincopyprint? 1. private ListView listView = new ListView();  Override onRender 方法,添加 DataProxy,DataReader 和 Loader 去获得 数据 [java] view plaincopyprint? 1. @Override 2. protected void onRender(Element parent, int index) { 3. super.onRender(parent, index); 4. final FeedServiceAsync feedService = (FeedServiceAsync) Registry 5. .get(RSSReaderConstants.FEED_SERVICE); 6. RpcProxy> proxy = new RpcProxy>() { 7. @Override 8. protected void load(Object loadConfig, 9. AsyncCallback> callback) { 10. feedService.loadFeedList(false, callback); 11. } 12. }; 13. BeanModelReader reader = new BeanModelReader(); 14. ListLoader> loader = new BaseListLoader>( 15. proxy, reader); 16. ListStore feedStore = new ListStore(loader); 17. loader.load(); 18. }  定义一个 getTemplate 方法,返回 XTemplate 的字符串。我们将用此 XTemplate 应用到 feed objects 中 imageUrl 不为空的数据上。 [java] view plaincopyprint? 1. private String getTemplate() { 2. StringBuilder sb = new StringBuilder(); 3. sb.append(""); 4. sb.append("
    "); 5. sb.append("

    {title}

    "); 6. sb.append(""); 7. sb.append(""); 8. sb.append(""); 9. sb.append("

    {description}

    "); 10. sb.append("
    "); 11. sb.append("
    "); 12. return sb.toString(); 13. }  回到onRender 方法上,将feedStore绑定到listView组件上,并设定Template。 之后将 listView 加入到 LayoutContainer [java] view plaincopyprint? 1. listView.setStore(feedStore); 2. listView.setTemplate(getTemplate()); 3. add(listView);  接下来,编写 war\items.css [css] view plaincopyprint? 1. div.feed-box { 2. float: left; 3. margin: 5px; 4. padding: 5px; 5. border: 1px solid black; 6. width: 200px; 7. height: 120px; 8. text-align: center; 9. } 10. 11. img.feed-thumbnail { 12. width: 100px; 13. height: 100px; 14. }  在主显示区,RssMainPanel。替换掉先前的 ItemGrid,加入 FeedOverviewView [java] view plaincopyprint? 1. add(new FeedOverviewView());  运行效果如下,内容显示区可以通过 css 进一步设定,让其超出部分隐藏起 来。  整个代码如下: [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.lists; 2. 3. import java.util.List; 4. 5. import com.danielvaughan.rssreader.client.RSSReaderConstants; 6. import com.danielvaughan.rssreader.client.services.FeedServiceAsync; 7. import com.danielvaughan.rssreader.shared.model.Feed; 8. import com.extjs.gxt.ui.client.Registry; 9. import com.extjs.gxt.ui.client.data.BaseListLoader; 10. import com.extjs.gxt.ui.client.data.BeanModel; 11. import com.extjs.gxt.ui.client.data.BeanModelReader; 12. import com.extjs.gxt.ui.client.data.ListLoadResult; 13. import com.extjs.gxt.ui.client.data.ListLoader; 14. import com.extjs.gxt.ui.client.data.RpcProxy; 15. import com.extjs.gxt.ui.client.store.ListStore; 16. import com.extjs.gxt.ui.client.widget.LayoutContainer; 17. import com.extjs.gxt.ui.client.widget.ListView; 18. import com.google.gwt.user.client.Element; 19. import com.google.gwt.user.client.rpc.AsyncCallback; 20. 21. public class FeedOverviewView extends LayoutContainer { 22. private ListView listView = new ListView(); 23. 24. private String getTemplate() { 25. StringBuilder sb = new StringBuilder(); 26. sb.append(""); 27. sb.append("
    "); 28. sb.append("

    {title}

    "); 29. sb.append(""); 30. sb.append(""); 31. sb.append(""); 32. sb.append("

    {description}

    "); 33. sb.append("
    "); 34. sb.append("
    "); 35. return sb.toString(); 36. } 37. 38. @Override 39. protected void onRender(Element parent, int index) { 40. super.onRender(parent, index); 41. final FeedServiceAsync feedService = (FeedServiceAsync) Registry 42. .get(RSSReaderConstants.FEED_SERVICE); 43. RpcProxy> proxy = new RpcProxy>() { 44. @Override 45. protected void load(Object loadConfig, 46. AsyncCallback> callback) { 47. feedService.loadFeedList(false, callback); 48. } 49. }; 50. BeanModelReader reader = new BeanModelReader(); 51. ListLoader> loader = new BaseListLoader>( 52. proxy, reader); 53. ListStore feedStore = new ListStore(loader); 54. loader.load(); 55. 56. listView.setStore(feedStore); 57. listView.setTemplate(getTemplate()); 58. add(listView); 59. } 60. } ModelProcesser 在这些数据并没有被真正的展示到 component 上之前,ModelProcesser 提供了通过 template 将数据预处理的功能。他不是真正的去修改 object 里 field 的值,而是会新建 fields 去包含和格式化结果集里面 fields,其使用过程和普通的 fields 一样。 举个例子,就像上一个 ListView 例子中,其中的一个 ListView 所显示的 feed,其 description 内容很长,会溢出于整个矩形。我们要做的就是使用 GXT 提供的 Format.ellipse 方法去缩 短其 description 的内容。这个缩短后的字符串,包含一少部分的字符,其末端用省略号连 接。 因此我们继续在 RSSReader 项目里加入 ModelProcessor 的代码。想要初始化数据,就要 overriding prepareData 方法。接下来我们就创建 title 和 description 的缩写 fields。  在 onRender 方法里,在 ListView 定义的时候,override ListView 的 prepareData 方法 [java] view plaincopyprint? 1. listView = new ListView() { 2. @Override 3. protected BeanModel prepareData(BeanModel feed) { 4. return feed; 5. } 6. };  上面的修改,只是返回一个原先的 feed 对象。接下来我们要给 feed 对象里 加入一个 shortTitle 的 field,其 value 是 title 字段的前 50 个字符。 shortDescription 是 descripton 的前 100 个字符 [java] view plaincopyprint? 1. listView = new ListView() { 2. @Override 3. protected BeanModel prepareData(BeanModel feed) { 4. feed.set("shortTitle", Format.ellipse((String) feed 5. .get("title"), 50)); 6. feed.set("shortDescription", Format.ellipse((String) feed 7. .get("description"), 100)); 8. return feed; 9. } 10. };  接下来,修改 getTemplate 方法的 template [java] view plaincopyprint? 1. private String getTemplate() { 2. StringBuilder sb = new StringBuilder(); 3. sb.append(""); 4. sb.append("
    "); 5. sb.append("

    {title}

    "); 6. sb.append(""); 7. sb 8. .append(""); 9. sb.append(""); 10. sb.append("

    {shortDescription}

    "); 11. sb.append("
    "); 12. sb.append("
    "); 13. return sb.toString(); 14. }  运行之后,那些讨厌的长字符就不会再次出现了。 Item selectors 现在的显示效果,或许大家会发现,在 FeedOverviewView 的 items 都不可以被选中。这是 因为我们没有设置其选中的功能。当然这些选中的效果是块选中。接下来,我们就使用 feed-box div 样式,让整个 FeedOverviewView 的 items 可以被选中,并且触发相应的 event  首先,在 onRender 方法里,设置 ListView 的 ItemSelector [java] view plaincopyprint? 1. listView.setItemSelector("div.feed-box");  接下来是处理选中事件。在 ListView 里添加一个 Listener 去监听 SelectionChange event。当用户选中某个 item 的时候,我们使用 Info 去显 示其 name field [java] view plaincopyprint? 1. listView.getSelectionModel().addListener(Events.SelectionChange, 2. new Listener>() { 3. public void handleEvent(SelectionChangedEvent be) { 4. BeanModel feed = (BeanModel) be.getSelection().get(0); 5. Info.display("Feed selected", (String) feed 6. .get("title")); 7. } 8. });  来看看运行效果 接下来,我们要跟进一步的操作 ListView 里 item 的显示内容。修改 FeedOverviewView 里 的 XTemplate,让一个 item 可以显示其里面的 children 内容。  要显示 item 里面的 children,就在 load 的时候,有 children 的数据。修改 RpcProxy,将 false 修改为 true。 [java] view plaincopyprint? 1. RpcProxy> proxy = new RpcProxy>() { 2. @Override 3. protected void load(Object loadConfig, 4. AsyncCallback> callback) { 5. feedService.loadFeedList(true, callback); 6. } 7. };  修改 getTemplate 方法 [java] view plaincopyprint? 1. private String getTemplate() { 2. StringBuilder sb = new StringBuilder(); 3. sb.append(""); 4. sb.append("
    "); 5. sb.append("

    {title}

    "); 6. sb.append(""); 7. sb 8. .append(""); 9. sb.append(""); 10. sb.append("

    {shortDescription}

    "); 11. sb.append("
      "); 12. sb.append(""); 13. sb.append(""); 14. sb.append("
    • {title}
    • "); 15. sb.append("
      "); 16. sb.append("
      "); 17. sb.append("
    "); 18. sb.append("
    "); 19. sb.append("
    "); 20. return sb.toString(); 21. } 第七章:Model View Controller 本章我们要了解 GXT 的 MVC 架构,以及学习他们在一个大型的应用系统里是如何系统工 作的 我们会涉及到如下 GXt 功能集  AppEvent  EventType  Controller  View  Dispatcher 一个好应用的需要 当我们在使用 GXT 搭建一个应用的时候,我认为最重要的是要如何去搭建他。当我们项目 在构建的时候,会很容易的发现各种各样的问题。比如组件越来越多,耦合度越来越大,这 会变得很难去跟踪和管理。这些情况会导致噩梦般的维护成本(当然了,也给我们程序员创 造了更多的就业机会 )。 对于桌面还是 web 可视化应用程序来说,目前最流行的就是 Model View Controller 模式的 架构方案。所幸,GXT 提供了基于的 MVC 模式实现方案,在此方案上架构的应用可以避免 不少麻烦! 经典的 MVC 模式 MVC 是一个非常流行的设计模式,虽然有几个不同的变化,但是基本上是由各尽其责,互 相协同工作的三部分组成的:  Model:他负责管理,状态,数据和应用逻辑。他提供接口,允许其管理的内 容被检索和修改。支持 Observer 设计模式,可以通过注册与唤醒的方式,作 为状态改变的响应。  View:是用户的操作接口。通过用户对数据的请求,View 作为响应则从 Model 里获得状态改变的内容,并展示给用户。当用户与 view 所提供的接口互相作 用,互相影响到时候,view 层提供了 event,会通过 controller 被触发事件。 view 层不需要考虑任何 controller 的工作原理。  Controller:controller 会作为一个 observer 的角色,监听 view 层所触发的 events。也可以修改 model 或者 view 的结果集。 这种模式的好处就是,可以让 model 层不需要去了解 controller 和 view 层是如何工作的, 从而让 model 与 controller 和 view 之间的没有彼此的依赖性。 GXT MVC GXT 的 MVC 模式有一点不同的传统的 MVC 模式,但是非常有用的!  Model:他是存储 ModelData 的 Store 的一种表现形式。专门负责:通过 get 方法,从 Store 里获得 ModelData,通过 set 方法,修改 ModelData。  View:他负责组织所有的 UI components。就像传统的 MVC 模式一样, data-backed components 检测 Model 的状态,当有变化的时候,会作出响 应。跟传统 MVC 不同的是,一旦 view 管理的 components 被灌入了 Model 里的数据,随着用户的操作,components 就会联动的修改 Model 里的数据。 view 使用分配器去派发事件,然后通过 controller 控制事件的响应。按照 GXT 的设计模式,view 需要通过其构造函数去关联有关 controller 层的对象。虽 然这样有些打破了传统的 MVC 模式——view 层和 controller 层没有完全的解 耦合。  Controller:在传统的 MVC 模式里,Controller 通过派发器对 view 层发出的 事件作出响应。在 GXT MVC 的模式里,他也可以表现为 Model 层面的某些 操作,或者可以将事件转发到别的 view 里。  Dispatcher:Dispatcher 的存在是为了避免 view 层直接调用 controller, view 通过 dispatcher 传播 event。Dispatcher 类提供一些静态方法可公调用,用 来将 event 传递到 controllers 里面。controller 会注册这些 dispatcher,用来 获得专门的事件类型。 GXT 之旅:第七章:MVC——GXT MVC 的相关类 分类: ExtGWT 2012-03-13 10:00578 人阅读评论(0)收藏举报 AppEvent class AppEvent 负责承载着信息,在 controller 和 view 之间。每个 AppEvent 对象都会有一个专 门的 EventType 定义。 AppEvent 对象可以随意的通过 setData 方法,承载一个或多个 datas。这样很有益于传递 状态信息。当设置多个 Datas 的时候,我们就需要使用 key-value 键值对,把每个 Data 命名,以方便检索。 另外一个功能就是,可是使用 setHistoryEvent 方法,在设置 AppEvent 的历史事件。这就 意味着,当一个事件被派发出去的时候,该之间就会被设置成历史事件。这一特性的设计, 方便在 dispatcher 里查询历史事件。 EventType class EventType 是用来定义事件类型的,具体来说是给 AppEvent 起名子的。具有代表性的,我 们会在 AppEvents 类里,定义一些列的静态 fields,器 field 的类别就是 EventType。 下面,我们就在 RSSReader 项目,定义应用事件  新建 package:com.danielvaughan.rssreader.client.mvc.events,新建类 AppEvents  在 AppEvents 类里,定义两个 EventType 的 fields [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.mvc.events; 2. 3. import com.extjs.gxt.ui.client.event.EventType; 4. 5. public class AppEvents { 6. public static final EventType Init = new EventType(); 7. public static final EventType Error = new EventType(); 8. } Controller class Controller 在应用里,承担着对事件响应和处理的角色。 一个 Controller 在其构造函数里,必须注册 EventTypes,才可以监听事件。 registerEventTypes 方法就是用通过传入 EventType 参数,完成注册过程!  新建 package:com.danielvaughan.rssreader.client.mvc.controllers,在此 包下,新建类 AppController 继承 Controller  在构造函数里,完成事件注册 [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.mvc.controllers; 2. 3. import com.danielvaughan.rssreader.client.mvc.events.AppEvents; 4. import com.extjs.gxt.ui.client.mvc.AppEvent; 5. import com.extjs.gxt.ui.client.mvc.Controller; 6. 7. public class AppController extends Controller { 8. public AppController() { 9. registerEventTypes(AppEvents.Init); 10. registerEventTypes(AppEvents.Error); 11. } 12. 13. @Override 14. public void handleEvent(AppEvent event) { 15. 16. 17. } 18. } 当继承抽象类 Controller 的时候,必须要实现 handleEvent 抽象方法。此方法是用来定义如 何处理不同 EventType 的。 如果想要查询一个 Controller 是否可以处理某个专门的 AppEvent 的时候,可以使用 canHandle 方法。 响应一个时间的处理流程如下:  事件出入总的 controller  然后将事件派发到具体的某一个子 controller  将事件转发到 view,等待后续的操作  将如上三步整合起来。 接下来,我们要在 RSSReader 项目实现,AppController 的 handleEvent 方法。  orverride handleEvent 方法  目前,我们不需要在 Controller 层处理任何的事件,只是将所有的事件转发 到 view 层。因此,我们需要定义一个 View [java] view plaincopyprint? 1. private com.extjs.gxt.ui.client.mvc.View appView;  随着 View 的定义,我们将所有的 evet 都转发到 view 里——实现一个 forward 的功能。 [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.mvc.controllers; 2. 3. import com.danielvaughan.rssreader.client.mvc.events.AppEvents; 4. import com.extjs.gxt.ui.client.mvc.AppEvent; 5. import com.extjs.gxt.ui.client.mvc.Controller; 6. import com.extjs.gxt.ui.client.mvc.View; 7. 8. public class AppController extends Controller { 9. 10. private View appView; 11. 12. public AppController() { 13. registerEventTypes(AppEvents.Init); 14. registerEventTypes(AppEvents.Error); 15. } 16. 17. @Override 18. public void handleEvent(AppEvent event) { 19. 20. forwardToView(appView, event); 21. } 22. } View class View是GXT MVC架构中一部分。负责提供用户接口——显示可视化组件,响应从Controller 转发过来的事件,当然也负责响应用户操作所产生的事件(AppEvents),并将其派发到 Dispatcher。 正如 controllers 一样,views 也需要实现 handleEvent 方法。为了保持代码整洁易懂,最好 针对每一个 EventType 去创建一个 on方法。 比如,如果我们想要处理 Init 的 EventType 。我们需要在 handleEvent 里检查其 EventType, 并调用对应的处理方法 onInit。 [java] view plaincopyprint? 1. protected void handleEvent(AppEvent event) { 2. EventType eventType = event.getType(); 3. if (eventType.equals(AppEvents.Init)) { 4. onInit(event); 5. } 6. } 接下来,我们在 RSSReader 项目里,实现 View 层的代码  新建 package:com.danielvaughan.rssreader.client.mvc.views,在此包下, 新建 AppView 继承 View  别忘了,orverride handleEvent 方法 [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.mvc.views; 2. 3. import com.extjs.gxt.ui.client.mvc.AppEvent; 4. import com.extjs.gxt.ui.client.mvc.Controller; 5. import com.extjs.gxt.ui.client.mvc.View; 6. 7. public class AppView extends View { 8. 9. public AppView(Controller controller) { 10. super(controller); 11. 12. } 13. 14. @Override 15. protected void handleEvent(AppEvent event) { 16. 17. } 18. 19. }  注意,如上的构造函数的写法。其原因是 View 没有无参的,空方法体的构造 函数!java 基础啦  正如我们之前介绍的:注册了两个事件 Init 和 Error,那么就要有对应的 on方法 [java] view plaincopyprint? 1. private void onInit(AppEvent event) {} 2. private void onError(AppEvent event) {}  现在,要实现 handleEvent 方法里面具体的内容——根据 EventType 跳转到 对应的处理方法。 [java] view plaincopyprint? 1. @Override 2. protected void handleEvent(AppEvent event) { 3. EventType eventType = event.getType(); 4. if (eventType.equals(AppEvents.Init)) { 5. onInit(event); 6. } else if (eventType.equals(AppEvents.Error)) { 7. onError(event); 8. } 9. }  最后,我们需要让 AppController 和 AppView 之间有关联。通过 override Appcontroller 的 initialize 方法实现。这样当 AppView 里面的事件才会通过 此关联操作,跳转到 AppController 中去! [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.mvc.controllers; 2. 3. import com.danielvaughan.rssreader.client.mvc.events.AppEvents; 4. import com.danielvaughan.rssreader.client.mvc.views.AppView; 5. import com.extjs.gxt.ui.client.mvc.AppEvent; 6. import com.extjs.gxt.ui.client.mvc.Controller; 7. import com.extjs.gxt.ui.client.mvc.View; 8. 9. public class AppController extends Controller { 10. 11. private View appView; 12. 13. public AppController() { 14. registerEventTypes(AppEvents.Init); 15. registerEventTypes(AppEvents.Error); 16. } 17. 18. @Override 19. public void handleEvent(AppEvent event) { 20. 21. forwardToView(appView, event); 22. } 23. 24. @Override 25. public void initialize() { 26. super.initialize(); 27. appView = new AppView(this); 28. } 29. } 30. 31. 32. package com.danielvaughan.rssreader.client.mvc.views; 33. 34. import com.danielvaughan.rssreader.client.mvc.events.AppEvents; 35. import com.extjs.gxt.ui.client.event.EventType; 36. import com.extjs.gxt.ui.client.mvc.AppEvent; 37. import com.extjs.gxt.ui.client.mvc.Controller; 38. import com.extjs.gxt.ui.client.mvc.View; 39. 40. public class AppView extends View { 41. 42. public AppView(Controller controller) { 43. super(controller); 44. 45. } 46. 47. @Override 48. protected void handleEvent(AppEvent event) { 49. EventType eventType = event.getType(); 50. if (eventType.equals(AppEvents.Init)) { 51. onInit(event); 52. } else if (eventType.equals(AppEvents.Error)) { 53. onError(event); 54. } 55. } 56. 57. private void onInit(AppEvent event) { 58. } 59. 60. private void onError(AppEvent event) { 61. } 62. } Dispatcher class Dispatcher 是一个单独的类——是一个单实例的对象,穿梭于整个应用。他有着 static 的方 法用来转发 AppEvent 对象。一个 Controller 需要通过 Dispatcher 去注册,然后监听不同 EventType 的 AppEvent。当一个 AppEvent 被派发的时候,就会根据先前注册监听的 EventType 去唤醒 AppEvent。 一个事件可以在程序的任何地方被唤醒,只是需要 Dispatcher 的 forwardEvent 方法。如下 有四个 forwardEvent 方法的重载,方便用户调用。 Method Description forwardEvent(AppEvent event) 将现有的 AppEvent 派发出去。 forwardEvent(EventType eventType) 根据 EventType,会新建一个 AppEvent,再派发出去。 forwardEvent(EventType eventType, java. lang.Object data) 根据 EventType 新建 AppEvent,作为一个载体,装载一个 object,并派发出去。 forwardEvent(EventType eventType, java. lang.Object data, boolean historyEvent) 根据 EventType 新建 AppEvent,装载一个 object。并标记的 AppEvent 是否是 一个历史事件,最后派发出去。 除了上述 static 的方法之外,Dispatcher 同样还有非静态的 dispatch 方法。当然功能上是 相同的。实是上,这些 static 的 forwardEvent 方法就是调用那些非静态的 dispatch 方法。 唯一不同的是,在这些非静态的 dispach 方法里,没有上述的第四个方法。 如果 Dispatcher 有多个 controllers 被注册了,那么他同样会唤醒所有的 AppEvent,其顺序 是按照当时注册的顺序执行。我们要注意到这一执行顺序,尤其是在 Controller 的 handleEvent 方法处理 AppEvent 的时候。 总结整个 GXT MVC 功能集 GXT MVC 的实现步骤如下: 1. 派发(Dispatcher)不同类型的 EventType 2. 监听 Dispatcher(派发器) 3. 接收事件,处理事件 4. 定义不同类型的 AppEvent 5. 程序处理:转发 AppEvent 到派发器 6. 程序处理:用户操作,触发了事件 7. 程序处理:将事件注册到 Controllers 里 8. 程序处理:将 Controllers 注册到派发器里(Dispatcher) GXT 之旅:第七章:MVC——MVC 重构项目(1) 分类: ExtGWT2012-03-13 15:27430 人阅读评论(0)收藏举报 使用 MVC 重构 RSSReader 项目 现在我们已经初步了解了 GXT MVC,现在我们就要利用此契机,将 RSSRead 项目重构一 下,使其应用于 MVC 架构——让 components 之间具有良好的一致性,而不是各自的分散 开来。 为了让 Controller 可以接收到 events,需要使用 Dispatcher 将其注册。通常情况我们都会 在程序入口类,来完成这一操作。那么我们就开动了~~~  在 com.danielvaughan.rssreader.client.RSSReader 类的 onModuleLoad 方 法里,删除掉所有的代码,仅仅保留注册 FeedService 的那一行。 [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client; 2. 3. import com.danielvaughan.rssreader.client.services.FeedService; 4. import com.extjs.gxt.ui.client.Registry; 5. import com.google.gwt.core.client.EntryPoint; 6. import com.google.gwt.core.client.GWT; 7. 8. /** 9. * Entry point classes define onModuleLoad(). 10. */ 11. public class RSSReader implements EntryPoint { 12. 13. /** 14. * This is the entry point method. 15. */ 16. @Override 17. public void onModuleLoad() { 18. Registry.register(RSSReaderConstants.FEED_SERVICE, 19. GWT.create(FeedService.class)); 20. 21. } 22. }  接下来,我们要获得 Dispatcher 的单例 [java] view plaincopyprint? 1. public void onModuleLoad() { 2. Registry.register(RSSReaderConstants.FEED_SERVICE, GWT 3. .create(FeedService.class)); 4. 5. Dispatcher dispatcher = Dispatcher.get(); 6. }  现在是最关键的,将 Controllers 注册到派发器里 [java] view plaincopyprint? 1. public void onModuleLoad() { 2. Registry.register(RSSReaderConstants.FEED_SERVICE, GWT 3. .create(FeedService.class)); 4. 5. Dispatcher dispatcher = Dispatcher.get(); 6. dispatcher.addController(new AppController()); 7. } 重构 UI 先前我们把 onModuleLoad 方法里面,关于 UI 部分的所有代码都删除了。现在我们要使用 MVC 的 view(AppView)带替换之前的 UI 代码。那么 AppView 的加载,将作为一个 Init EventType 事件来处理——既然是初始化的事件,那么其触发点,就在程序的初始化时候, 也就是在 onModuleLoad 方法里。 (我个人感觉就是使用了 MVC 之后,一切操作的源 头都要是事件,不要直接的硬编码,所有的编码都要在对事件响应的处理方法里完成)  现在,找到 onModuleLoad 方法,同样的使用 dispatcher 去派发事件 [java] view plaincopyprint? 1. public void onModuleLoad() { 2. Registry.register(RSSReaderConstants.FEED_SERVICE, GWT 3. .create(FeedService.class)); 4. 5. Dispatcher dispatcher = Dispatcher.get(); 6. dispatcher.addController(new AppController()); 7. dispatcher.dispatch(AppEvents.Init); 8. }  根据之前的 AppController 的事件处理,他会自动的将事件转发到 AppView 的 handleEvent 方法中去。那么根据 Init 事件的处理,就是去完成程序整体 的 UI 的创建,因此我们接下来的工作要在 AppView 类里面完成。在 AppView 类,创建两个属性——ContentPanel ,Viewport [java] view plaincopyprint? 1. private final ContentPanel mainPanel = new ContentPanel(); 2. final Viewport viewport = new Viewport();  先前我们已经创建好了空方法 onInit,就是用来处理 Init 事件的,所以整个的 UI 操作在此方法里实现(其实就是把原先在 onModuleLoad 里有关 UI 的代 码,迁移到 onInit 方法里)。 [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.mvc.views; 2. 3. import com.danielvaughan.rssreader.client.mvc.events.AppEvents; 4. import com.extjs.gxt.ui.client.Style.LayoutRegion; 5. import com.extjs.gxt.ui.client.Style.Orientation; 6. import com.extjs.gxt.ui.client.event.EventType; 7. import com.extjs.gxt.ui.client.mvc.AppEvent; 8. import com.extjs.gxt.ui.client.mvc.Controller; 9. import com.extjs.gxt.ui.client.mvc.View; 10. import com.extjs.gxt.ui.client.widget.ContentPanel; 11. import com.extjs.gxt.ui.client.widget.Viewport; 12. import com.extjs.gxt.ui.client.widget.layout.BorderLayout; 13. import com.extjs.gxt.ui.client.widget.layout.BorderLayoutData; 14. import com.extjs.gxt.ui.client.widget.layout.RowLayout; 15. import com.google.gwt.user.client.ui.HTML; 16. 17. public class AppView extends View { 18. private final ContentPanel mainPanel = new ContentPanel(); 19. private final Viewport viewport = new Viewport(); 20. 21. public AppView(Controller controller) { 22. super(controller); 23. 24. } 25. 26. @Override 27. protected void handleEvent(AppEvent event) { 28. EventType eventType = event.getType(); 29. if (eventType.equals(AppEvents.Init)) { 30. onInit(event); 31. } else if (eventType.equals(AppEvents.Error)) { 32. onError(event); 33. } 34. } 35. 36. private void onInit(AppEvent event) { 37. final BorderLayout borderLayout = new BorderLayout(); 38. viewport.setLayout(borderLayout); 39. HTML headerHtml = new HTML(); 40. headerHtml.setHTML("

    RSS Reader

    "); 41. BorderLayoutData northData = new BorderLayoutData(LayoutRegion.NORTH, 42. 20); 43. northData.setCollapsible(false); 44. northData.setSplit(false); 45. viewport.add(headerHtml, northData); 46. BorderLayoutData centerData = new BorderLayoutData(LayoutRegion.CENTER); 47. centerData.setCollapsible(false); 48. RowLayout rowLayout = new RowLayout(Orientation.VERTICAL); 49. mainPanel.setHeaderVisible(false); 50. mainPanel.setLayout(rowLayout); 51. viewport.add(mainPanel, centerData); 52. } 53. 54. private void onError(AppEvent event) { 55. } 56. }  此时,如果大家现在就运行程序的话,只能看到等待消息的页面,原因是因 为我们还没有把组装好的 viewport 添加到应用里。我习惯上,把组装的过程 和添加的过程分成两个不同的事件去处理(事件细化),这样一来代码清晰 明确,通过看事件,就可以轻松了解程序的运行步骤。  在 com.danielvaughan.rssreader.client.mvc.events.AppEvents,定义一个新 的 EventType——UIReady,用来作为处理 Viewport 的事件 [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.mvc.events; 2. 3. import com.extjs.gxt.ui.client.event.EventType; 4. 5. public class AppEvents { 6. public static final EventType Init = new EventType(); 7. public static final EventType Error = new EventType(); 8. public static final EventType UIReady = new EventType(); 9. }  在 AppController 类里,注册 UIReady 事件 [java] view plaincopyprint? 1. public AppController() { 2. registerEventTypes(AppEvents.Init); 3. registerEventTypes(AppEvents.Error); 4. registerEventTypes(AppEvents.UIReady); 5. }  在 AppView 类里,新建方法 onUIReady,将 Viewport 加入到 RootPanel [java] view plaincopyprint? 1. private void onUIReady(AppEvent event) { 2. RootPanel.get().add(viewport); 3. }  当然不要忘记,AppView 的 handleEvent 方法。将传入的 event 响应到 onUIReady 方法里 [java] view plaincopyprint? 1. @Override 2. protected void handleEvent(AppEvent event) { 3. EventType eventType = event.getType(); 4. if (eventType.equals(AppEvents.Init)) { 5. onInit(event); 6. } else if (eventType.equals(AppEvents.Error)) { 7. onError(event); 8. } else if (eventType.equals(AppEvents.UIReady)) { 9. onUIReady(event); 10. } 11. }  最后是,派发 UIReady 事件:RSSReader 类的 onModuleLoad 方法,派发 事件 [java] view plaincopyprint? 1. @Override 2. public void onModuleLoad() { 3. Registry.register(RSSReaderConstants.FEED_SERVICE, GWT 4. .create(FeedService.class)); 5. 6. Dispatcher dispatcher = Dispatcher.get(); 7. dispatcher.addController(new AppController()); 8. dispatcher.dispatch(AppEvents.Init); 9. dispatcher.dispatch(AppEvents.UIReady); 10. }  运行后的效果如下: GXT 之旅:第七章:MVC——MVC 重构项目(2) 分类: ExtGWT2012-03-15 17:39389 人阅读评论(0)收藏举报 使用 MVC 重构 RSSReader 项目——Navigation 区 域 上一节,在程序的入口文件(RSSReader),我们派发了 EventType 为 Init 的 AppEvent, AppController 会处理此事件,将其转发到 AppView。依次的,AppView 会的处理该事件, 去调用 onInit 方法,完成基础的 UI 创建。在此过程中负责了程序主体的区域创建。但是, 唯一没有处理的,就是没有把组装好的 components 添加到 UI。 那么现在,我们需要再分别创建几组 Controller 和 View,分别去负责 RSSReader 项目其 他的各个区域。首先我们要创建一对 Controller-View,去处理 Navigation 区域。 大致思路如下:  负责 navigation 区域的 controller,能够将 Init EventType 转发到对应的 View 里  负责 navigation 区域的 view,会处理 Init EventType,在处理的过程中,又 会派发一个新的事件(NavPanelReady)——此事件会承载着 NavPanel 的 实例被派发到 Dispatcher 中。  AppController 会监听此事件(NavPanelReady),当接收到此事件传入的时 候,会将其转发到 AppView。  AppView 会处理会理NavPanelReady的事件——将其传递的NavPanel 实例, 加入到 ViewPort 上 此前,onModuleLoad 方法里已经派发了 UIReady 事件。因此,NavPanel 应该已经被添加 到 Viewport 里,显示出来。具体实现步骤如下:  在 com.danielvaughan.rssreader.client.mvc.events.AppEvents 类,加入新的 EventType——NavPanelReady [java] view plaincopyprint? 1. public static final EventType NavPanelReady = new EventType();  新建 NavController extends Controller [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.mvc.controllers; 2. 3. import com.extjs.gxt.ui.client.mvc.AppEvent; 4. import com.extjs.gxt.ui.client.mvc.Controller; 5. 6. public class NavController extends Controller { 7. 8. @Override 9. public void handleEvent(AppEvent event) { 10. 11. 12. } 13. 14. }  新建 NavView extends View [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.mvc.views; 2. 3. import com.extjs.gxt.ui.client.mvc.AppEvent; 4. import com.extjs.gxt.ui.client.mvc.Controller; 5. import com.extjs.gxt.ui.client.mvc.View; 6. 7. public class NavView extends View { 8. 9. public NavView(Controller controller) { 10. super(controller); 11. } 12. 13. @Override 14. protected void handleEvent(AppEvent event) { 15. 16. } 17. 18. }  在 NavController 的构造函数里,注册 Init EventType [java] view plaincopyprint? 1. public NavController() { 2. registerEventTypes(AppEvents.Init); 3. }  在 NavController,定义 NavView 类实例,通过其 initialize 方法实例化 NavView——让 controller 与 view 关联起来。实现 handleEvent 方法,将 NavController 接收的事件转发到 NavView 里。 [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.mvc.controllers; 2. 3. import com.danielvaughan.rssreader.client.mvc.events.AppEvents; 4. import com.danielvaughan.rssreader.client.mvc.views.NavView; 5. import com.extjs.gxt.ui.client.mvc.AppEvent; 6. import com.extjs.gxt.ui.client.mvc.Controller; 7. 8. public class NavController extends Controller { 9. 10. private NavView navView; 11. 12. public NavController() { 13. registerEventTypes(AppEvents.Init); 14. } 15. 16. @Override 17. public void handleEvent(AppEvent event) { 18. forwardToView(navView, event); 19. } 20. 21. @Override 22. public void initialize() { 23. super.initialize(); 24. navView = new NavView(this); 25. } 26. 27. }  将 com.danielvaughan.rssreader.client.components.RssNavigationPanel 类, 重命名为 NavPanel。然后在 NavView 类里,加入 NavPanel 的实例属性 [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.mvc.views; 2. 3. import com.danielvaughan.rssreader.client.components.NavPanel; 4. import com.extjs.gxt.ui.client.mvc.AppEvent; 5. import com.extjs.gxt.ui.client.mvc.Controller; 6. import com.extjs.gxt.ui.client.mvc.View; 7. 8. public class NavView extends View { 9. 10. private final NavPanel navPanel = new NavPanel(); 11. 12. public NavView(Controller controller) { 13. super(controller); 14. } 15. 16. @Override 17. protected void handleEvent(AppEvent event) { 18. 19. } 20. 21. }  在 NavView 类里实现 handleEvent 方法——当 Init EventType 接收到之后, 再派发一个新事件(此过程我习惯称之事件联动):NavPanelReady ,并且 派发的同时,承载 navPanel 对象 [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.mvc.views; 2. 3. import com.danielvaughan.rssreader.client.components.NavPanel; 4. import com.danielvaughan.rssreader.client.mvc.events.AppEvents; 5. import com.extjs.gxt.ui.client.event.EventType; 6. import com.extjs.gxt.ui.client.mvc.AppEvent; 7. import com.extjs.gxt.ui.client.mvc.Controller; 8. import com.extjs.gxt.ui.client.mvc.Dispatcher; 9. import com.extjs.gxt.ui.client.mvc.View; 10. 11. public class NavView extends View { 12. 13. private final NavPanel navPanel = new NavPanel(); 14. 15. public NavView(Controller controller) { 16. super(controller); 17. } 18. 19. @Override 20. protected void handleEvent(AppEvent event) { 21. EventType eventType = event.getType(); 22. 23. if (eventType.equals(AppEvents.Init)) { 24. Dispatcher.forwardEvent(new AppEvent(AppEvents.NavPanelReady, 25. navPanel)); 26. } 27. } 28. 29. }  既然 NavView 会派发 NavPanelReady 事件,我们希望交给 AppController 注册,也就意味着,AppView 会接收到此事件,做后续的操作。 [java] view plaincopyprint? 1. public AppController() { 2. registerEventTypes(AppEvents.Init); 3. registerEventTypes(AppEvents.Error); 4. registerEventTypes(AppEvents.UIReady); 5. registerEventTypes(AppEvents.NavPanelReady); 6. }  在 AppView 对 NavPanelReady 事件的处理实现如下——将 NavPanelReady 事件传递的 navPanel 对象,对后续处理,将其添加到 viewport 上 [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.mvc.views; 2. 3. import com.danielvaughan.rssreader.client.mvc.events.AppEvents; 4. import com.extjs.gxt.ui.client.Style.LayoutRegion; 5. import com.extjs.gxt.ui.client.Style.Orientation; 6. import com.extjs.gxt.ui.client.event.EventType; 7. import com.extjs.gxt.ui.client.mvc.AppEvent; 8. import com.extjs.gxt.ui.client.mvc.Controller; 9. import com.extjs.gxt.ui.client.mvc.View; 10. import com.extjs.gxt.ui.client.widget.Component; 11. import com.extjs.gxt.ui.client.widget.ContentPanel; 12. import com.extjs.gxt.ui.client.widget.Viewport; 13. import com.extjs.gxt.ui.client.widget.layout.BorderLayout; 14. import com.extjs.gxt.ui.client.widget.layout.BorderLayoutData; 15. import com.extjs.gxt.ui.client.widget.layout.RowLayout; 16. import com.google.gwt.user.client.ui.HTML; 17. import com.google.gwt.user.client.ui.RootPanel; 18. 19. public class AppView extends View { 20. private final ContentPanel mainPanel = new ContentPanel(); 21. private final Viewport viewport = new Viewport(); 22. 23. public AppView(Controller controller) { 24. super(controller); 25. 26. } 27. 28. @Override 29. protected void handleEvent(AppEvent event) { 30. EventType eventType = event.getType(); 31. if (eventType.equals(AppEvents.Init)) { 32. onInit(event); 33. } else if (eventType.equals(AppEvents.Error)) { 34. onError(event); 35. } else if (eventType.equals(AppEvents.UIReady)) { 36. onUIReady(event); 37. }else if (eventType.equals(AppEvents.NavPanelReady)) { 38. onNavPanelReady(event); 39. } 40. } 41. 42. private void onInit(AppEvent event) { 43. final BorderLayout borderLayout = new BorderLayout(); 44. viewport.setLayout(borderLayout); 45. HTML headerHtml = new HTML(); 46. headerHtml.setHTML("

    RSS Reader

    "); 47. BorderLayoutData northData = new BorderLayoutData(LayoutRegion.NORTH, 48. 20); 49. northData.setCollapsible(false); 50. northData.setSplit(false); 51. viewport.add(headerHtml, northData); 52. BorderLayoutData centerData = new BorderLayoutData(LayoutRegion.CENTER); 53. centerData.setCollapsible(false); 54. RowLayout rowLayout = new RowLayout(Orientation.VERTICAL); 55. mainPanel.setHeaderVisible(false); 56. mainPanel.setLayout(rowLayout); 57. viewport.add(mainPanel, centerData); 58. } 59. 60. private void onUIReady(AppEvent event) { 61. RootPanel.get().add(viewport); 62. } 63. 64. private void onNavPanelReady(AppEvent event) { 65. BorderLayoutData westData = new BorderLayoutData(LayoutRegion.WEST, 66. 200, 150, 300); 67. westData.setCollapsible(true); 68. westData.setSplit(true); 69. Component component = event.getData(); 70. viewport.add(component, westData); 71. } 72. 73. private void onError(AppEvent event) { 74. } 75. }  所有的准备工作做好了,那么一切事件的触发点,我们要回到 RSSReader 的 onModuleLoad 方法里。加入 NavController [java] view plaincopyprint? 1. @Override 2. public void onModuleLoad() { 3. final FeedServiceAsync feedService = GWT.create(FeedService.class); 4. Registry.register(RSSReaderConstants.FEED_SERVICE, feedService); 5. Dispatcher dispatcher = Dispatcher.get(); 6. 7. dispatcher.addController(new AppController()); 8. dispatcher.addController(new NavController()); 9. 10. // 注意:dispatcher.dispatch(AppEvents.Init);会派发 Init 事件,虽然只是执行 了一次派发操作,但是会派发到多个 controller 中去! 11. // 原因:因为 AppController 和 NavController 都注册了 Init ! 12. // 顺序:两个 controller 的接收到 event 的顺序是根据上面的两行代码(controller 的加入顺序)有关! 13. dispatcher.dispatch(AppEvents.Init); 14. dispatcher.dispatch(AppEvents.UIReady); 15. }  程序运行效果如下: GXT 之旅:第七章:MVC——MVC 重构项目(3) 分类: ExtGWT2012-03-19 15:51343 人阅读评论(0)收藏举报 使用 MVC 重构 RSSReader 项目——FeedPanel 区 域 下面,按照同样的逻辑,去处理 FeedPanel 区域。  在 AppEvents 类,定义一个新的 EventType——FeedPanelReady [java] view plaincopyprint? 1. public static final EventType FeedPanelReady = new EventType();  在 package:com.danielvaughan.rssreader.client.mvc.controllers,新建类 FeedController extends Controller [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.mvc.controllers; 2. 3. import com.extjs.gxt.ui.client.mvc.AppEvent; 4. import com.extjs.gxt.ui.client.mvc.Controller; 5. 6. public class FeedController extends Controller { 7. 8. @Override 9. public void handleEvent(AppEvent event) { 10. 11. 12. } 13. 14. }  在 package:com.danielvaughan.rssreader.client.mvc.views,新建 FeedView extends View [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.mvc.views; 2. 3. import com.extjs.gxt.ui.client.mvc.AppEvent; 4. import com.extjs.gxt.ui.client.mvc.Controller; 5. import com.extjs.gxt.ui.client.mvc.View; 6. 7. public class FeedView extends View { 8. 9. public FeedView(Controller controller) { 10. super(controller); 11. 12. } 13. 14. @Override 15. protected void handleEvent(AppEvent event) { 16. } 17. 18. }  在 FeedController 的构造函数里,注册 Init EventType [java] view plaincopyprint? 1. public FeedController() { 2. registerEventTypes(AppEvents.Init); 3. }  在 FeedController 类里,定义 FeedView 属性,在 initialize 方法了,实例化 他。并且实现 handleEvent 方法,将接收的 event 转发到 feedview 里。 [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.mvc.controllers; 2. 3. import com.danielvaughan.rssreader.client.mvc.events.AppEvents; 4. import com.danielvaughan.rssreader.client.mvc.views.FeedView; 5. import com.extjs.gxt.ui.client.mvc.AppEvent; 6. import com.extjs.gxt.ui.client.mvc.Controller; 7. 8. public class FeedController extends Controller { 9. 10. private FeedView feedView; 11. 12. public FeedController() { 13. registerEventTypes(AppEvents.Init); 14. } 15. 16. @Override 17. public void handleEvent(AppEvent event) { 18. forwardToView(feedView, event);//同样的,将接收到的 event 转发到 feedView 里 19. } 20. 21. @Override 22. public void initialize() { 23. super.initialize(); 24. feedView = new FeedView(this); 25. } 26. 27. }  重命名 RssMainPanel 为 FeedPanel。在 FeedView 里加入 FeedPanel 的实 例属性 [java] view plaincopyprint? 1. private final FeedPanel feedPanel = new FeedPanel();  在 FeedView 类里,加入 onInit 的对应事件处理方法。其 onInit 方法的内容 是派发另一个事件 FeedPanelReady。并且处理 handleEvent 方法,根据 Init 事件类型,调用 onInti 方法 [java] view plaincopyprint? 1. @Override 2. protected void handleEvent(AppEvent event) { 3. EventType eventType = event.getType(); 4. if (eventType.equals(AppEvents.Init)) { 5. onInit(event); 6. } 7. } 8. 9. private void onInit(AppEvent event) { 10. Dispatcher.forwardEvent(new AppEvent(AppEvents.FeedPanelReady, 11. feedPanel)); 12. }  在 AppControllers 类里,注册 FeedPanelReady 事件 [java] view plaincopyprint? 1. public AppController() { 2. registerEventTypes(AppEvents.Init); 3. registerEventTypes(AppEvents.Error); 4. registerEventTypes(AppEvents.UIReady); 5. registerEventTypes(AppEvents.NavPanelReady); 6. registerEventTypes(AppEvents.FeedPanelReady); 7. }  在 AppView 类里,编写处理 FeedPanelReady 事件的对应方法 ——onFeedPanelReady。并且修改 AppView 的 handleEvent 方法 [java] view plaincopyprint? 1. @Override 2. protected void handleEvent(AppEvent event) { 3. EventType eventType = event.getType(); 4. if (eventType.equals(AppEvents.Init)) { 5. onInit(event); 6. } else if (eventType.equals(AppEvents.Error)) { 7. onError(event); 8. } else if (eventType.equals(AppEvents.UIReady)) { 9. onUIReady(event); 10. } else if (eventType.equals(AppEvents.NavPanelReady)) { 11. onNavPanelReady(event); 12. } else if (eventType.equals(AppEvents.FeedPanelReady)) { 13. onFeedPanelReady(event); 14. } 15. } 16. private void onFeedPanelReady(AppEvent event) { 17. RowData rowData = new RowData(); 18. rowData.setHeight(.5); 19. Component component = event.getData(); 20. mainPanel.add(component, rowData); 21. }  最后,在 RSSReader 的 onModule,加入 FeedController 控制器 [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client; 2. 3. import com.danielvaughan.rssreader.client.mvc.controllers.AppController; 4. import com.danielvaughan.rssreader.client.mvc.controllers.FeedController; 5. import com.danielvaughan.rssreader.client.mvc.controllers.NavController; 6. import com.danielvaughan.rssreader.client.mvc.events.AppEvents; 7. import com.danielvaughan.rssreader.client.services.FeedService; 8. import com.danielvaughan.rssreader.client.services.FeedServiceAsync; 9. import com.extjs.gxt.ui.client.Registry; 10. import com.extjs.gxt.ui.client.mvc.Dispatcher; 11. import com.google.gwt.core.client.EntryPoint; 12. import com.google.gwt.core.client.GWT; 13. 14. /** 15. * Entry point classes define onModuleLoad(). 16. */ 17. public class RSSReader implements EntryPoint { 18. 19. /** 20. * This is the entry point method. 21. */ 22. @Override 23. public void onModuleLoad() { 24. final FeedServiceAsync feedService = GWT.create(FeedService.class); 25. Registry.register(RSSReaderConstants.FEED_SERVICE, feedService); 26. Dispatcher dispatcher = Dispatcher.get(); 27. 28. dispatcher.addController(new AppController()); 29. dispatcher.addController(new NavController()); 30. dispatcher.addController(new FeedController()); 31. // 注意:dispatcher.dispatch(AppEvents.Init);会派发 Init 事件,虽然只是执行 了一次派发操作,但是会派发到多个 controller 中去! 32. // 原因:因为 AppController 和 NavController 都注册了 Init ! 33. // 顺序:两个 controller 的接收到 event 的顺序是根据上面的两行代码(controller 的加入顺序)有关! 34. 35. dispatcher.dispatch(AppEvents.Init); 36. dispatcher.dispatch(AppEvents.UIReady); 37. } 38. }  最后执行效果如下  FeedPanel 类的完整内容如下(取出之前的例子代码): [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.components; 2. 3. import com.extjs.gxt.ui.client.widget.ContentPanel; 4. import com.extjs.gxt.ui.client.widget.layout.FitLayout; 5. 6. public class FeedPanel extends ContentPanel { 7. public FeedPanel() { 8. setHeading("Main"); 9. setLayout(new FitLayout()); 10. } 11. } GXT 之旅:第七章:MVC——MVC 重构项目(4) 分类: ExtGWT2012-03-22 11:41493 人阅读评论(6)收藏举报 使用 MVC 重构 RSSReader 项目——Item 区域 之前,我们已经创建了两个非常类似的 Controller 和 View,分别服务于 NavPanel 和 FeedPanel。现在我们继续按照同样的思路来构建 ItemPanel——ItemPanelReady (EventType),ItemController 和 ItemView。 我们的目的是生成如下样式的应用效果:  AppEvents class [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.mvc.events; 2. 3. import com.extjs.gxt.ui.client.event.EventType; 4. 5. public class AppEvents { 6. public static final EventType Init = new EventType(); 7. public static final EventType Error = new EventType(); 8. public static final EventType UIReady = new EventType(); 9. public static final EventType NavPanelReady = new EventType(); 10. public static final EventType FeedPanelReady = new EventType(); 11. public static final EventType ItemPanelReady = new EventType(); 12. }  AppController class [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.mvc.controllers; 2. 3. import com.danielvaughan.rssreader.client.mvc.events.AppEvents; 4. import com.danielvaughan.rssreader.client.mvc.views.AppView; 5. import com.extjs.gxt.ui.client.mvc.AppEvent; 6. import com.extjs.gxt.ui.client.mvc.Controller; 7. import com.extjs.gxt.ui.client.mvc.View; 8. 9. public class AppController extends Controller { 10. 11. private View appView; 12. 13. public AppController() { 14. registerEventTypes(AppEvents.Init); 15. registerEventTypes(AppEvents.Error); 16. registerEventTypes(AppEvents.UIReady); 17. registerEventTypes(AppEvents.NavPanelReady); 18. registerEventTypes(AppEvents.FeedPanelReady); 19. registerEventTypes(AppEvents.ItemPanelReady); 20. } 21. 22. @Override 23. public void handleEvent(AppEvent event) { 24. 25. forwardToView(appView, event); 26. } 27. 28. @Override 29. public void initialize() { 30. super.initialize(); 31. appView = new AppView(this); 32. } 33. }  RSSReader class [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client; 2. 3. import com.danielvaughan.rssreader.client.mvc.controllers.AppController; 4. import com.danielvaughan.rssreader.client.mvc.controllers.FeedController; 5. import com.danielvaughan.rssreader.client.mvc.controllers.NavController; 6. import com.danielvaughan.rssreader.client.mvc.events.AppEvents; 7. import com.danielvaughan.rssreader.client.services.FeedService; 8. import com.danielvaughan.rssreader.client.services.FeedServiceAsync; 9. import com.extjs.gxt.ui.client.Registry; 10. import com.extjs.gxt.ui.client.mvc.Dispatcher; 11. import com.google.gwt.core.client.EntryPoint; 12. import com.google.gwt.core.client.GWT; 13. 14. /** 15. * Entry point classes define onModuleLoad(). 16. */ 17. public class RSSReader implements EntryPoint { 18. 19. /** 20. * This is the entry point method. 21. */ 22. @Override 23. public void onModuleLoad() { 24. final FeedServiceAsync feedService = GWT.create(FeedService.class); 25. Registry.register(RSSReaderConstants.FEED_SERVICE, feedService); 26. Dispatcher dispatcher = Dispatcher.get(); 27. 28. dispatcher.addController(new AppController()); 29. dispatcher.addController(new NavController()); 30. dispatcher.addController(new FeedController()); 31. dispatcher.addController(new ItemController()); 32. 33. dispatcher.dispatch(AppEvents.Init); 34. dispatcher.dispatch(AppEvents.UIReady); 35. } 36. }  ItemController class [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.mvc.controllers; 2. 3. import com.danielvaughan.rssreader.client.mvc.events.AppEvents; 4. import com.danielvaughan.rssreader.client.mvc.views.ItemView; 5. import com.extjs.gxt.ui.client.mvc.AppEvent; 6. import com.extjs.gxt.ui.client.mvc.Controller; 7. 8. public class ItemController extends Controller { 9. 10. private ItemView itemView; 11. 12. public ItemController() { 13. registerEventTypes(AppEvents.Init); 14. } 15. 16. @Override 17. public void handleEvent(AppEvent event) { 18. forwardToView(itemView, event); 19. } 20. 21. @Override 22. public void initialize() { 23. super.initialize(); 24. itemView = new ItemView(this); 25. } 26. }  ItemView class: [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.mvc.views; 2. 3. import com.danielvaughan.rssreader.client.components.ItemPanel; 4. import com.danielvaughan.rssreader.client.mvc.events.AppEvents; 5. import com.extjs.gxt.ui.client.event.EventType; 6. import com.extjs.gxt.ui.client.mvc.AppEvent; 7. import com.extjs.gxt.ui.client.mvc.Controller; 8. import com.extjs.gxt.ui.client.mvc.Dispatcher; 9. import com.extjs.gxt.ui.client.mvc.View; 10. 11. public class ItemView extends View { 12. private final ItemPanel itemPanel = new ItemPanel(); 13. 14. public ItemView(Controller controller) { 15. super(controller); 16. } 17. 18. @Override 19. protected void handleEvent(AppEvent event) { 20. EventType eventType = event.getType(); 21. if (eventType.equals(AppEvents.Init)) { 22. Dispatcher.forwardEvent(new AppEvent(AppEvents.ItemPanelReady, 23. itemPanel)); 24. } 25. } 26. 27. }  AppView class: [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.mvc.views; 2. 3. import com.danielvaughan.rssreader.client.mvc.events.AppEvents; 4. import com.extjs.gxt.ui.client.Style.LayoutRegion; 5. import com.extjs.gxt.ui.client.Style.Orientation; 6. import com.extjs.gxt.ui.client.event.EventType; 7. import com.extjs.gxt.ui.client.mvc.AppEvent; 8. import com.extjs.gxt.ui.client.mvc.Controller; 9. import com.extjs.gxt.ui.client.mvc.View; 10. import com.extjs.gxt.ui.client.widget.Component; 11. import com.extjs.gxt.ui.client.widget.ContentPanel; 12. import com.extjs.gxt.ui.client.widget.Viewport; 13. import com.extjs.gxt.ui.client.widget.layout.BorderLayout; 14. import com.extjs.gxt.ui.client.widget.layout.BorderLayoutData; 15. import com.extjs.gxt.ui.client.widget.layout.RowData; 16. import com.extjs.gxt.ui.client.widget.layout.RowLayout; 17. import com.google.gwt.user.client.ui.HTML; 18. import com.google.gwt.user.client.ui.RootPanel; 19. 20. public class AppView extends View { 21. private final ContentPanel mainPanel = new ContentPanel(); 22. private final Viewport viewport = new Viewport(); 23. 24. public AppView(Controller controller) { 25. super(controller); 26. 27. } 28. 29. @Override 30. protected void handleEvent(AppEvent event) { 31. EventType eventType = event.getType(); 32. if (eventType.equals(AppEvents.Init)) { 33. onInit(event); 34. } else if (eventType.equals(AppEvents.Error)) { 35. onError(event); 36. } else if (eventType.equals(AppEvents.UIReady)) { 37. onUIReady(event); 38. } else if (eventType.equals(AppEvents.NavPanelReady)) { 39. onNavPanelReady(event); 40. } else if (eventType.equals(AppEvents.FeedPanelReady)) { 41. onFeedPanelReady(event); 42. } else if (eventType.equals(AppEvents.ItemPanelReady)) { 43. onItemPanelReady(event); 44. } 45. } 46. 47. private void onInit(AppEvent event) { 48. final BorderLayout borderLayout = new BorderLayout(); 49. viewport.setLayout(borderLayout); 50. HTML headerHtml = new HTML(); 51. headerHtml.setHTML("

    RSS Reader

    "); 52. BorderLayoutData northData = new BorderLayoutData(LayoutRegion.NORTH, 53. 20); 54. northData.setCollapsible(false); 55. northData.setSplit(false); 56. viewport.add(headerHtml, northData); 57. BorderLayoutData centerData = new BorderLayoutData(LayoutRegion.CENTER); 58. centerData.setCollapsible(false); 59. RowLayout rowLayout = new RowLayout(Orientation.VERTICAL); 60. mainPanel.setHeaderVisible(false); 61. mainPanel.setLayout(rowLayout); 62. viewport.add(mainPanel, centerData); 63. } 64. 65. private void onNavPanelReady(AppEvent event) { 66. BorderLayoutData westData = new BorderLayoutData(LayoutRegion.WEST, 67. 200, 150, 300); 68. westData.setCollapsible(true); 69. westData.setSplit(true); 70. Component component = event.getData(); 71. viewport.add(component, westData); 72. } 73. 74. private void onFeedPanelReady(AppEvent event) { 75. RowData rowData = new RowData(); 76. rowData.setHeight(.5); 77. Component component = event.getData(); 78. mainPanel.add(component, rowData); 79. } 80. 81. private void onItemPanelReady(AppEvent event) { 82. RowData rowData = new RowData(); 83. rowData.setHeight(.5); 84. Component component = event.getData(); 85. mainPanel.add(component, rowData); 86. } 87. 88. private void onUIReady(AppEvent event) { 89. RootPanel.get().add(viewport); 90. } 91. 92. private void onError(AppEvent event) { 93. } 94. } GXT 之旅:第七章:MVC——Navigation,Main 和 Item 区 域交互(1) 分类: ExtGWT2012-04-12 12:55486 人阅读评论(3)收藏举报 (刚干了一个多月的 flex 项目,一时半会还无法转过神来专注于 GXT 的东东,有好多东西自己 都忘的差不多了。。。。 我得好好屡屡 ) 使用 TabPanel 显示 feeds 之前,我们已经通过 RssMainPanel 里的 ItemGrid 来显示一个 feed 数据。现在,我们将要使用 TabPanel 去管理多个 TabItem——其中每一个 TabItem 包含一个 ItemGrid,一个 ItemGrid 负 责显示一个 feed 数据的多个 Items 内容。  编辑 com.danielvaughan.rssreader.client.components.FeedPanel,新建 field——TabPanel [java] view plaincopyprint? 1. private final TabPanel tabPanel = new TabPanel();  新建一个方法——addTab,传入 TabItem 参数。设置一些属性,如下: [java] view plaincopyprint? 1. public void addTab(TabItem tabItem) { 2. tabItem.setLayout(new FitLayout()); 3. tabItem.setIcon(Resources.ICONS.rss()); 4. tabItem.setScrollMode(Scroll.AUTO); 5. }  因为我们只是想让一个 TabItem 负责显示一个 feed 数据内容,所以当一个 TabItem 已经拥有 feed 数据显示到TabPanel 的时候,就直接跳转到此TabItem, 否则就创建一个新的 TabItem [java] view plaincopyprint? 1. public void addTab(TabItem tabItem) { 2. tabItem.setLayout(new FitLayout()); 3. tabItem.setIcon(Resources.ICONS.rss()); 4. tabItem.setScrollMode(Scroll.AUTO); 5. 6. String tabId = tabItem.getId(); 7. TabItem existingTab = tabPanel.findItem(tabId, false); 8. if (existingTab == null) { 9. tabPanel.add(tabItem); 10. tabPanel.setSelection(tabItem); 11. } else { 12. tabPanel.setSelection(existingTab); 13. } 14. }  重构 FeedPanel 构造函数: [java] view plaincopyprint? 1. public FeedPanel() { 2. setHeading("Main"); 3. setLayout(new FitLayout()); 4. add(tabPanel); 5. } 连接起来 我们现在几乎已经准备好了所有的 UI,现在我们需要将它们连接起来——当用户在 Main(FeedPanel)区域选择了某条 feed,则在 Item(ItemPanel)区域显示出具体的内容。 我们可以根据用户的选择事件操作,将其选择的 ModelDate 在不同的 components 之间传递。 同样的,也可以通过数据的加载事件,将 ModelData 在不同的 components 之间传递。 大致思路如下:  当用户在 FeedList 选中了一个 Feed(link)的时候,一个 FeedSelected AppEvent 就会搭载其 Feed 被派发出去。  当 FeedSelected AppEvent 被派发出来之后,FeedView 接收到此事件,然后负责 页面跳转到或新建一个 ItemGrid 来显示 Feed   当用户在 ItemGrid 选中了一个 Item 的时候,一个 ItemSelected AppEvent 就会 搭载其 Item 被派发出去。  当 ItemSelected AppEvent 被派发出来之后,ItemView 接收到此事件,然后负责 页面使用 ItemPanel 来显示 Item   当用户在 TabPanel 选中某个 tab 的时候,一个 TabSelected AppEvent 就会搭载 其 Feed 被派发出去。  当 TabSelectedAppEvent 被派发出来之后,FeedList 接收到此事件,然后负责联 动选中到对应的 Feed。 Implements:  AppEvents 类,新定义三个 EventType [java] view plaincopyprint? 1. public static final EventType FeedSelected = new EventType(); 2. public static final EventType ItemSelected = new EventType(); 3. public static final EventType TabSelected = new EventType();  找到 com.danielvaughan.rssreader.client.lists.FeedList 类,在其 onRender 方法 里,添加 SelectionChange 监听——当用户选择不同的 Feed 的时候,就会触发 SelectionChangedEvent 事件,其操作过程,是将传入的 selectedItem,派发出 去。 [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.lists; 2. 3. import java.util.List; 4. 5. import com.danielvaughan.rssreader.client.RSSReaderConstants; 6. import com.danielvaughan.rssreader.client.mvc.events.AppEvents; 7. import com.danielvaughan.rssreader.client.services.FeedServiceAsync; 8. import com.danielvaughan.rssreader.shared.model.Feed; 9. import com.extjs.gxt.ui.client.Registry; 10. import com.extjs.gxt.ui.client.data.BaseListLoader; 11. import com.extjs.gxt.ui.client.data.BeanModel; 12. import com.extjs.gxt.ui.client.data.BeanModelReader; 13. import com.extjs.gxt.ui.client.data.ListLoadResult; 14. import com.extjs.gxt.ui.client.data.ListLoader; 15. import com.extjs.gxt.ui.client.data.LoadEvent; 16. import com.extjs.gxt.ui.client.data.RpcProxy; 17. import com.extjs.gxt.ui.client.event.LoadListener; 18. import com.extjs.gxt.ui.client.event.SelectionChangedEvent; 19. import com.extjs.gxt.ui.client.event.SelectionChangedListener; 20. import com.extjs.gxt.ui.client.mvc.Dispatcher; 21. import com.extjs.gxt.ui.client.store.ListStore; 22. import com.extjs.gxt.ui.client.widget.LayoutContainer; 23. import com.extjs.gxt.ui.client.widget.form.ListField; 24. import com.extjs.gxt.ui.client.widget.layout.FitLayout; 25. import com.google.gwt.user.client.Element; 26. import com.google.gwt.user.client.rpc.AsyncCallback; 27. 28. public class FeedList extends LayoutContainer { 29. 30. final ListField feedList = new ListField(); 31. 32. public FeedList() { 33. setLayout(new FitLayout()); 34. } 35. 36. private String getTemplate() { 37. StringBuilder sb = new StringBuilder(); 38. sb.append(""); 39. sb.append("
    {title} -{description}
    "); 40. sb.append("
    "); 41. return sb.toString(); 42. } 43. 44. @Override 45. protected void onRender(Element parent, int index) { 46. super.onRender(parent, index); 47. // 0:从 Registry 里获得 Service 48. final FeedServiceAsync feedService = (FeedServiceAsync) Registry 49. .get(RSSReaderConstants.FEED_SERVICE); 50. // 1:定义 proxy 在 load 方法里掉用 Serivce 里的方法 51. RpcProxy> proxy = new RpcProxy>() { 52. @Override 53. protected void load(Object loadConfig, 54. AsyncCallback> callback) { 55. feedService.loadFeedList(false, callback); 56. 57. } 58. }; 59. // 2:定义 Reader 60. BeanModelReader reader = new BeanModelReader(); 61. // 3:将 proxy 和 reader 传入,定义 loader 62. ListLoader> loader = new BaseListLoader>( 63. proxy, reader); 64. // 4:传入 loader,生成 store,此时还没有 load 数据 65. final ListStore feedStore = new ListStore(loader); 66. // 5:将 stroe 绑定到 data-backed component 身上 67. feedList.setStore(feedStore); 68. feedList.setTemplate(getTemplate()); 69. 70. feedList.addSelectionChangedListener(new SelectionChangedListener() { 71. @Override 72. public void selectionChanged(SelectionChangedEvent se) { 73. Feed feed = se.getSelectedItem().getBean(); 74. if (feed != null) { 75. Dispatcher.forwardEvent(AppEvents.FeedSelected, feed);//关键在这里~~~ 76. } 77. } 78. }); 79. 80. // 6:真正的 load 数据,load 成功之后,data-backed component 会自动的显示出来。 81. loader.load(); 82. 83. add(feedList); 84. } 85. }  如果想要程序能够对 FeedSelected 事件作出响应,就必须在关联的 Controller 注 册——FeedController [java] view plaincopyprint? 1. public FeedController() { 2. registerEventTypes(AppEvents.Init); 3. 4. registerEventTypes(AppEvents.FeedSelected); 5. }  重构 ItemGrid 的构造函数,增加一个参数为 Feed——用来接收 FeedSelected 事 件传递过来的 Feed [java] view plaincopyprint? 1. private final Feed feed; 2. public ItemGrid(Feed feed) { 3. setLayout(new FitLayout()); 4. this.feed = feed; 5. }  继续在 ItemGrid 类,将 onRender 方法里面局部变量的 grid,提取到方法外面, 作为 ItemGrid 类的属性存在。 [java] view plaincopyprint? 1. private Grid grid; 2. 3. @Override 4. protected void onRender(Element parent, int index) { 5. … 6. grid = new Grid(itemStore, columnModel); 7. }  继续在 ItemGrid 类的 onRender 方法里。删除如下局部变量的定义 [java] view plaincopyprint? 1. final String TEST_DATA_FILE = "http://localhost:8888/rss2sample.xml";  取而代之,是使用 Feed 对象的 UUID 属性替换之前的 TEST_DATA_FILE [java] view plaincopyprint? 1. RpcProxy> proxy = new RpcProxy>() { 2. @Override 3. protected void load(Object loadConfig, 4. AsyncCallback> callback) { 5. feedService.loadItems(feed.getUuid(), callback); 6. } 7. };  继续在 ItemGrid 类里,定义一个新的方法——resetSelection,用来重设 Grid 的选中状态。 [java] view plaincopyprint? 1. public void resetSelection() { 2. grid.getSelectionModel().deselectAll(); 3. }  在 FeedView 类,新建一个方法(onFeedSelected)用来响应,接收到的 FeedSelected 事件:通过传入的 event 获得 Feed 对象;将 Feed 对象作为参数创 建一个 ItemGrid;新建 TabItem,并设置一些属性,将先前新建的 ItemGrid 加 入到 Tabitem 里;最后,将 tabItem 加入到 feedPanel 里。 [java] view plaincopyprint? 1. private void onFeedSelected(AppEvent event) { 2. Feed feed = event.getData(); 3. final ItemGrid itemGrid = new ItemGrid(feed); 4. TabItem tabItem = new TabItem(feed.getTitle()); 5. tabItem.setId(feed.getUuid()); 6. tabItem.setData("feed", feed); 7. tabItem.add(itemGrid); 8. tabItem.addListener(Events.Select, new Listener() {//选 中之后的 tab 会清空 item selection 9. @Override 10. public void handleEvent(TabPanelEvent be) { 11. itemGrid.resetSelection(); 12. } 13. }); 14. tabItem.setClosable(true); 15. feedPanel.addTab(tabItem); 16. }  在 FeedView 类的 handleEvent 方法里,加入对 FeedSelected 事件的跳转。到此 FeedSelected 事件的处理流程完成 [java] view plaincopyprint? 1. @Override 2. protected void handleEvent(AppEvent event) { 3. EventType eventType = event.getType(); 4. if (eventType.equals(AppEvents.Init)) { 5. onInit(event); 6. }else if (eventType.equals(AppEvents.FeedSelected)) { 7. onFeedSelected(event); 8. } 9. }  在 ItemGrid 的 onRender 方法里,添加一个 SelectionChange 的监听,当用户选 择不同的 Item 的时候,就会触发 SelectionChangedEvent 事件,其操作过程,是 将传入的 selectedItem,派发出去。 [java] view plaincopyprint? 1. grid.getSelectionModel().addListener(Events.SelectionChange, 2. new Listener>() { 3. @Override 4. public void handleEvent(SelectionChangedEvent be) { 5. Item item = be.getSelectedItem(); 6. if(item!=null) 7. Dispatcher.forwardEvent(AppEvents.ItemSelected, item); 8. } 9. });  同样的,对于 ItemSelected 方法,我们要在 ItemController 里注册 [java] view plaincopyprint? 1. public ItemController() { 2. registerEventTypes(AppEvents.Init); 3. 4. registerEventTypes(AppEvents.ItemSelected); 5. }  在ItemView类里,针对ItemSelected AppEvent处理的方法——onItemSelected() [java] view plaincopyprint? 1. private void onItemSelected(AppEvent event) { 2. Item item = (Item) event.getData(); 3. itemPanel.displayItem(item); 4. }  在 ItemView 类里,handleEvent 方法里加入针对 ItemSelected 事件的处理方法 跳转。到此 ItemSelected 事件的处理流程完成 [java] view plaincopyprint? 1. @Override 2. protected void handleEvent(AppEvent event) { 3. EventType eventType = event.getType(); 4. if (eventType.equals(AppEvents.Init)) { 5. Dispatcher.forwardEvent(new AppEvent(AppEvents.ItemPanelReady, 6. itemPanel)); 7. } else if (eventType.equals(AppEvents.ItemSelected)) { 8. onItemSelected(event); 9. } 10. }  现在我们还少一个事件处理流程——TabSelected ,但是我们先来看看程序运行 效果哈~~~ GXT 之旅:第七章:MVC——Navigation,Main 和 Item 区 域交互(2) 分类: ExtGWT2012-04-17 16:06291 人阅读评论(0)收藏举报 保持同步 我们现在需要保证,当有新的 feed 添加的时候,feeds 列表要正确的更新出来。同时,我 们也需要保证,当用户选择了某个 feed 的 tab 的时候,相对应于 feeds 列表中的 feed 会同 步的被选中。 为了实现如上两个要求,我们要创建两个对应的 events——一个 feed 被添加,一个 feed tab 被选中  在 AppEvents 类里定义两个新的 EventType——FeedAdded,TabSelected [java] view plaincopyprint? 1. public static final EventType TabSelected = new EventType(); 2. public static final EventType FeedAdded = new EventType();  上面两个事件都属于左侧导航区域应该响应的事件。因此,在 NavController 类里面注册 [java] view plaincopyprint? 1. public NavController() { 2. registerEventTypes(AppEvents.Init); 3. registerEventTypes(AppEvents.FeedAdded); 4. registerEventTypes(AppEvents.TabSelected); 5. }  在 FeedList 类里,将 ListField 和 ListLoader 的定义从 onRender 方法里提出 来 [java] view plaincopyprint? 1. private final ListField feedList = new 2. ListField(); 3. private ListLoader> loader;  继续重构,定义一个新的方法 reloadFeeds,其功能是负责 load 真正的数据。 [java] view plaincopyprint? 1. public void reloadFeeds() { 2. loader.load(); 3. }  定义第二个新方法 selectFeed,其功能是,使用参数 feed 去设置,选中适当 的 ListField [java] view plaincopyprint? 1. public void selectFeed(Feed feed) { 2. BeanModelFactory beanModelFactory = BeanModelLookup.get().getFactory( 3. feed.getClass()); 4. feedList.setSelection(Arrays.asList(beanModelFactory.createModel(feed) )); 5. }  在 NavPanel 类里,重构一下其构造函数,将匿名对象 new FeedList();提为 属性 [java] view plaincopyprint? 1. private FeedList feedList = new FeedList(); 2. 3. public NavPanel() { 4. setHeading("Navigation"); 5. setLayout(new FitLayout()); 6. initToolbar(); 7. add(feedList); 8. }  在 NavPanel 类里,再定义两个新方法——selectFeed,reloadFeeds 方法, 相同于 FeedList 类里的方法。 [java] view plaincopyprint? 1. public void reloadFeeds() { 2. feedList.reloadFeeds(); 3. } 4. 5. public void selectFeed(Feed feed) { 6. feedList.selectFeed(feed); 7. }  在 NavView 类里,新建 onTabSelected 方法,通过 AppEvent 获得其携带的 feed,feed 用于调用 selectFeed 方法传入参数。 [java] view plaincopyprint? 1. private void onTabSelected(AppEvent event) { 2. Feed feed = (Feed) event.getData(); 3. navPanel.selectFeed(feed); 4. }  继续,在 NavView 类里,新建 onFeedAdded 方法,调用 NavPanel.reloadFeeds 方法 [java] view plaincopyprint? 1. private void onFeedAdded(AppEvent event) { 2. navPanel.reloadFeeds(); 3. }  在 NavView 类里,开始修改 handleEvent 方法,根据事件的类型,调用与之 对应的处理方法 [java] view plaincopyprint? 1. @Override 2. protected void handleEvent(AppEvent event) { 3. EventType eventType = event.getType(); 4. 5. if (eventType.equals(AppEvents.Init)) { 6. Dispatcher.forwardEvent(new AppEvent(AppEvents.NavPanelReady, 7. navPanel)); 8. } else if (eventType.equals(AppEvents.TabSelected)) { 9. onTabSelected(event); 10. } else if (eventType.equals(AppEvents.FeedAdded)) { 11. onFeedAdded(event); 12. } 13. }  在 LinkFeedPopup 类里的 addFeed 方法里,之前我们通过调用 FeedService.addExistingFeed 方法来添加一个 feed 的 link。接下来在其 onSuccess 方法我们触发 FeedAdded 事件 [java] view plaincopyprint? 1. public void addFeed(final String feedUrl) { 2. final FeedServiceAsync feedService = Registry 3. .get(RSSReaderConstants.FEED_SERVICE); 4. feedService.addExistingFeed(feedUrl, new AsyncCallback() { 5. @Override 6. public void onFailure(Throwable caught) { 7. Info.display("RSS Reader", "Failed to add feed at: " + feedUrl); 8. } 9. 10. @Override 11. public void onSuccess(Void result) { 12. tfUrl.clear(); 13. Info.display("RSS Reader", "Feed at " + feedUrl 14. + " added successfully"); 15. Dispatcher.forwardEvent(AppEvents.FeedAdded); 16. hide(); 17. } 18. }); 19. }  类似的,在 FeedForm.save 方法里,在调用 FeedService.saveFeed 方法时 的 onSuccess 方法里,加入同样的触发 FeedAdded 事件 [java] view plaincopyprint? 1. public void save(final Feed feed) { 2. feed.setTitle(tfTitle.getValue()); 3. feed.setDescription(taDescription.getValue()); 4. feed.setLink(tfLink.getValue()); 5. 6. final FeedServiceAsync feedService = Registry 7. .get(RSSReaderConstants.FEED_SERVICE); 8. feedService.saveFeed(feed, new AsyncCallback() { 9. @Override 10. public void onFailure(Throwable caught) { 11. Info.display("RSS Reader", 12. "Failed to save feed: " + feed.getTitle()); 13. } 14. 15. @Override 16. public void onSuccess(Void result) { 17. Info.display("RSS Reader", "Feed " + feed.getTitle() 18. + " saved sucessfully"); 19. Dispatcher.forwardEvent(AppEvents.FeedAdded); 20. } 21. }); 22. 23. }  最后,在 FeedView.onFeedSelected 方法里,在现有的 Listener 内触发 TabSelected 事件 [java] view plaincopyprint? 1. private void onFeedSelected(AppEvent event) { 2. final Feed feed = event.getData(); 3. final ItemGrid itemGrid = new ItemGrid(feed); 4. TabItem tabItem = new TabItem(feed.getTitle()); 5. tabItem.setId(feed.getUuid()); 6. tabItem.setData("feed", feed); 7. tabItem.add(itemGrid); 8. tabItem.addListener(Events.Select, new Listener() {// 选 中之后的 tab 清空 item 9. // selection 10. @Override 11. public void handleEvent(TabPanelEvent be) { 12. itemGrid.resetSelection(); 13. Dispatcher.forwardEvent(new AppEvent( 14. AppEvents.TabSelected, feed)); 15. } 16. }); 17. tabItem.setClosable(true); 18. feedPanel.addTab(tabItem); 19. } GXT 之旅:第七章:MVC——Status toolbar 分类: ExtGWT2012-04-17 17:41289 人阅读评论(0)收藏举报 一个 AppEvent 不仅仅可以被一个 Controller 所捕捉,它可以被多个 Controller 所捕捉,关 键在于有多少个 Controller 注册了该 AppEvent。 下面我们要结合之前代码,新建一个新的 StatusController,注册之前已有的 Events,使用 StatusToolbar component 来给用户显示系统的运行状态。 我们现在针对两个以后的事件:  Feed selected  Item selected 下面具体实现步骤如下:  AppEvents 类里,加入新的 EventType 定义——StatusToolbarReady [java] view plaincopyprint? 1. public static final EventType StatusToolbarReady = new EventType();  在 com.danielvaughan.rssreader.client.mvc.controllers 包下,新建类 ——StatusController。具体内容如下 [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.mvc.controllers; 2. 3. import com.danielvaughan.rssreader.client.mvc.events.AppEvents; 4. import com.danielvaughan.rssreader.client.mvc.views.StatusView; 5. import com.extjs.gxt.ui.client.mvc.AppEvent; 6. import com.extjs.gxt.ui.client.mvc.Controller; 7. 8. public class StatusController extends Controller { 9. 10. private StatusView statusView; 11. 12. public StatusController() { 13. registerEventTypes(AppEvents.Init); 14. registerEventTypes(AppEvents.Error); 15. registerEventTypes(AppEvents.UIReady); 16. registerEventTypes(AppEvents.FeedSelected); 17. registerEventTypes(AppEvents.ItemSelected); 18. } 19. 20. @Override 21. public void handleEvent(AppEvent event) { 22. forwardToView(statusView, event); 23. } 24. 25. @Override 26. public void initialize() { 27. super.initialize(); 28. statusView = new StatusView(this); 29. } 30. }  在 com.danielvaughan.rssreader.client.mvc.views 包下,新建 StatusView。 具体实现如下 [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.mvc.views; 2. 3. import com.extjs.gxt.ui.client.mvc.AppEvent; 4. import com.extjs.gxt.ui.client.mvc.Controller; 5. import com.extjs.gxt.ui.client.mvc.View; 6. 7. public class StatusView extends View { 8. 9. public StatusView(StatusController statusController) { 10. super(statusController); 11. } 12. 13. @Override 14. protected void handleEvent(AppEvent event) { 15. 16. } 17. 18. }  新建好后,再定义两个新的属性——Status 和 ToolBar。在定义一个新的方 法,用来设置 Status 的显示内容 [java] view plaincopyprint? 1. private final Status status = new Status(); 2. private final ToolBar toolBar = new ToolBar(); 3. public void setStatus(String message) { 4. status.setText(message); 5. }  继续在 StatusView 类里,新建方法 onInit,用来对应 Init 事件处理。其内容 一部分是装配 Status 和 ToolBar;另一部分是派发 StatusToolbarReady事件, 并携带刚刚装配好的 toolBar [java] view plaincopyprint? 1. private void onInit() { 2. status.setWidth("100%"); 3. status.setBox(true); 4. toolBar.add(status); 5. Dispatcher.forwardEvent(new AppEvent(AppEvents.StatusToolbarReady, 6. toolBar)); 7. }  自然的,要在 handleEvent 方法里编写 Init 事件的流程控制。 [java] view plaincopyprint? 1. @Override 2. protected void handleEvent(AppEvent event) { 3. EventType eventType = event.getType(); 4. if (eventType.equals(AppEvents.Init)) { 5. onInit(); 6. setStatus("Init"); 7. } 8. }  针对于*Ready 的事件,其捕捉的 Controller 都是 AppController,因此要在 AppController 注册 StatusPanelReady [java] view plaincopyprint? 1. public AppController() { 2. registerEventTypes(AppEvents.Init); 3. registerEventTypes(AppEvents.Error); 4. registerEventTypes(AppEvents.UIReady); 5. registerEventTypes(AppEvents.NavPanelReady); 6. registerEventTypes(AppEvents.FeedPanelReady); 7. registerEventTypes(AppEvents.ItemPanelReady); 8. 9. registerEventTypes(AppEvents.StatusToolbarReady); 10. }  随着 AppController 里有新的事件被注册了,因此就要在响应的 View 里编写 事件的处理方法 ——编辑 AppView 类,新建 onStatusToolbarReady 方 法,其内容是把 AppEvent 所携带的 Component,加入到 mainPanel 里显示 出来~~~ [java] view plaincopyprint? 1. private void onStatusToolbarReady(AppEvent event) { 2. Component component = event.getData(); 3. mainPanel.setBottomComponent(component); 4. }  编辑 AppView 类 handleEvent 方法,针对 StatusPanelReady 事件的流程处 理。 [java] view plaincopyprint? 1. @Override 2. protected void handleEvent(AppEvent event) { 3. EventType eventType = event.getType(); 4. if (eventType.equals(AppEvents.Init)) { 5. onInit(event); 6. } else if (eventType.equals(AppEvents.Error)) { 7. onError(event); 8. } else if (eventType.equals(AppEvents.UIReady)) { 9. onUIReady(event); 10. } else if (eventType.equals(AppEvents.NavPanelReady)) { 11. onNavPanelReady(event); 12. } else if (eventType.equals(AppEvents.FeedPanelReady)) { 13. onFeedPanelReady(event); 14. } else if (eventType.equals(AppEvents.ItemPanelReady)) { 15. onItemPanelReady(event); 16. }else if (eventType.equals(AppEvents.StatusToolbarReady)) { 17. onStatusToolbarReady(event); 18. } 19. }  好像大家都忘了,事件需要被某些 Controllers 里注册,同样的 Controllers 也需要在应用里注册!编辑整个应用的入口类——RSSReader,注册 StatusController [java] view plaincopyprint? 1. @Override 2. public void onModuleLoad() { 3. final FeedServiceAsync feedService = GWT.create(FeedService.class); 4. Registry.register(RSSReaderConstants.FEED_SERVICE, feedService); 5. Dispatcher dispatcher = Dispatcher.get(); 6. 7. dispatcher.addController(new AppController()); 8. dispatcher.addController(new NavController()); 9. dispatcher.addController(new FeedController()); 10. dispatcher.addController(new ItemController()); 11. 12. dispatcher.addController(new StatusController());//here 13. 14. dispatcher.dispatch(AppEvents.Init); 15. dispatcher.dispatch(AppEvents.UIReady); 16. }  回归到 StatusView 类,开始编写针对 FeedSelected 事件的处理。(因为 StatusController 在最后被注册,所以 FeedSelected 事件会先被派发到 FeedController,最后才是 StatusController。) [java] view plaincopyprint? 1. @Override 2. protected void handleEvent(AppEvent event) { 3. EventType eventType = event.getType(); 4. if (eventType.equals(AppEvents.Init)) { 5. onInit(); 6. setStatus("Init"); 7. } else if (eventType.equals(AppEvents.FeedSelected)) { 8. Feed feed = event.getData(); 9. setStatus("Feed Selected - (" + feed.getTitle() + ")"); 10. } 11. }  相似的,在 StatusView 类,编写针对 ItemSelected 事件的处理。 [java] view plaincopyprint? 1. @Override 2. protected void handleEvent(AppEvent event) { 3. EventType eventType = event.getType(); 4. if (eventType.equals(AppEvents.Init)) { 5. onInit(); 6. setStatus("Init"); 7. } else if (eventType.equals(AppEvents.FeedSelected)) { 8. Feed feed = event.getData(); 9. setStatus("Feed Selected - (" + feed.getTitle() + ")"); 10. } else if (eventType.equals(AppEvents.ItemSelected)) { 11. Item item = event.getData(); 12. setStatus("Item Selected - (" + item.getTitle() + ")"); 13. } 14. }  运行效果如下: GXT 之旅:第八章:Portal&Drag-Drop——Portal 的基本介 绍 分类: ExtGWT2012-04-23 12:03296 人阅读评论(0)收藏举报 第八章:Portal&Drag-Drop 本章我们要了解 GXT 的 Portal&Drag-Drop 功能。我们会首先学习如何使用 Portal(非常类 似谷歌的 iGoogle 功能)布局和 Portlet,然后再以实践的方式学习 GXT 的拖拽功能。 我们会涉及到如下 GXt 功能集  Portal  Portlet  Draggable  DragSource o GridDropTarget o ListViewDropTarget o TreeGridDropTarget o TreePanelDropTarget  ColumnLayout  RowLayout Portlet class Portlet 类继承自 ContentPanel,她提供了一种特殊的 panel,可以在 Viewport 的 Portal 容器里,随意的更改显示位置和大小。她很像 widows 系统里面的桌面应用。 新建一个 Portlet 的过程很类似于其他容器的创建。 [java] view plaincopyprint? 1. Portlet portlet = new Portlet(); 2. portlet.setHeight(150); 3. portlet.setHeading("Example Portlet"); 运行效果如下: [java] view plaincopyprint? 1. portal.setPinned(true); 设置其是否可以被重置位置。除此之外,Portlet 继承 ContentPanel 的所有功能 Portal class Portal 是专门为 Portlet 设置的容器。事实上,其容器包含了一组按照 ColumnLayout 布局 的 LayoutContainer。其中每一个 LayoutContainer 都包含一个 Portlet,布局效果为 RowLayout。 Portal 同样支持 Portlet 的拖拽功能。Portal 里面会有列,每一个列里面又包含行。因此就 有了表格的概念,所以当新建一个 Portal 的时候,我们就需要在构造函数里,设置有多少 列。当然还得通过 setColumnWidth()方法设置其每一列的宽度。 假如,我们想创建一个两列的,分别是 30%,70%宽的 Portal 的时候。我们会有如下代码 [java] view plaincopyprint? 1. Portal portal = new Portal(2); 2. portal.setColumnWidth(0, 0.3); 3. portal.setColumnWidth(1, 0.7); 当 Portal 定义好了之后,我们要往每一列里放入 Portlet(其宽度自动的跟着 column 的宽度 适应,只需要设置高度既可) [java] view plaincopyprint? 1. Portlet portlet1 = new Portlet(); 2. portlet1.setHeight(150); 3. portlet1.setHeading("Example Portlet 1"); 4. portal.add(portlet1, 0); 5. 6. Portlet portlet2 = new Portlet(); 7. portlet2.setHeight(150); 8. portlet2.setHeading("Example Portlet 2"); 9. portal.add(portlet2, 1); 生成后的效果如下: 左右两个 Portlet 可以被拖拽到不同的位置。当一个 Portlet 正在被拖拽的时候,其效果如下 当右侧的 portlet 被拖拽到左侧的时候,会自动的改变宽度 ToolButton 正是因为 Portlet 继承了 ContentPanel,所以我们可以在其头部添加 ToolButton。这样一来, 可以让 Portlet 在功能和显示效果上,更接近桌面的应用窗口。 [java] view plaincopyprint? 1. portlet.getHeader().addTool(new ToolButton("x-tool-minimize")); 2. portlet.getHeader().addTool(new ToolButton("x-tool-maximize")); 3. portlet.getHeader().addTool(new ToolButton("x-tool-close")); 如上的三行代码,就会构建出如下的效果 GXT 之旅:第八章:Portal&Drag-Drop——项目使用 Portal 重构(1) 分类: ExtGWT 2012-04-23 14:16236 人阅读评论(0)收藏举报 目前,我们的 RSSReader 项目里,使用的是 ContentPanel 容器和 BorderLayout 布局效果。 接下来,我们要做稍微的调整,将 ContentPanel 替换为 Portlet, 然后使用 Portal 去管理他们。 Portlet 是一个非常理想的 components,用来构成独立的,不受他人影响的用户接口。不仅 仅可以统一的被 Portal 管理,我们还可以使用 MVC,让 Portal 响应每一个 Portlet 的新建 过程,并保证其独立性。  我们首先要做的是新增一个 EventType(NewPortletCreated)到 AppEvents 类里。我们会用它来触发新建 Portlet 过程。 [java] view plaincopyprint? 1. public static final EventType NewPortletCreated = new EventType();  新建类:PortalController extends Controller [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.mvc.controllers; 2. 3. import com.extjs.gxt.ui.client.mvc.AppEvent; 4. import com.extjs.gxt.ui.client.mvc.Controller; 5. 6. public class PortalController extends Controller { 7. 8. public PortalController() { 9. 10. } 11. 12. @Override 13. public void handleEvent(AppEvent event) { 14. 15. } 16. 17. }  新建类:PortalView extends View [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.mvc.views; 2. 3. import com.danielvaughan.rssreader.client.mvc.controllers.PortalController; 4. import com.extjs.gxt.ui.client.mvc.AppEvent; 5. import com.extjs.gxt.ui.client.mvc.View; 6. 7. public class PortalView extends View { 8. 9. public PortalView(PortalController controller) { 10. super(controller); 11. } 12. 13. @Override 14. protected void handleEvent(AppEvent event) { 15. 16. } 17. 18. }  回到 PortalController 类,加入 PortalView 属性到此类里。并且 Override initialize()方法 [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.mvc.controllers; 2. 3. import com.danielvaughan.rssreader.client.mvc.views.PortalView; 4. import com.extjs.gxt.ui.client.mvc.AppEvent; 5. import com.extjs.gxt.ui.client.mvc.Controller; 6. 7. public class PortalController extends Controller { 8. 9. private PortalView portalView; 10. 11. @Override 12. public void initialize() { 13. super.initialize(); 14. portalView = new PortalView(this); 15. } 16. 17. public PortalController() { 18. 19. } 20. 21. @Override 22. public void handleEvent(AppEvent event) { 23. 24. } 25. 26. }  构造函数里,注册 EventType——NewPortletCreated,Error [java] view plaincopyprint? 1. public PortalController() { 2. registerEventTypes(AppEvents.NewPortletCreated); 3. registerEventTypes(AppEvents.Error); 4. }  下面处理 handleEvent 方法——有错误事件的时候,记录日志;其他情况, 将事件转发到 portalView [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.mvc.controllers; 2. 3. import com.danielvaughan.rssreader.client.mvc.events.AppEvents; 4. import com.danielvaughan.rssreader.client.mvc.views.PortalView; 5. import com.extjs.gxt.ui.client.event.EventType; 6. import com.extjs.gxt.ui.client.mvc.AppEvent; 7. import com.extjs.gxt.ui.client.mvc.Controller; 8. import com.google.gwt.core.client.GWT; 9. 10. public class PortalController extends Controller { 11. 12. private PortalView portalView; 13. 14. @Override 15. public void initialize() { 16. super.initialize(); 17. portalView = new PortalView(this); 18. } 19. 20. public PortalController() { 21. registerEventTypes(AppEvents.NewPortletCreated); 22. registerEventTypes(AppEvents.Error); 23. } 24. 25. @Override 26. public void handleEvent(AppEvent event) { 27. EventType eventType = event.getType(); 28. if (eventType.equals(AppEvents.Error)) { 29. GWT.log("Error", (Throwable) event.getData()); 30. } else { 31. forwardToView(portalView, event); 32. } 33. } 34. 35. }  接下来回到 PortalView 类里,新建一个属性——portal,并且是一个具有两 列的 portal [java] view plaincopyprint? 1. private final Portal portal = new Portal(2);  Override initialize()方法:设置两列的各自宽度;并且新建一个 Viewport,在 Viewport 里加入 portal [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.mvc.views; 2. 3. import com.danielvaughan.rssreader.client.mvc.controllers.PortalController; 4. import com.extjs.gxt.ui.client.mvc.AppEvent; 5. import com.extjs.gxt.ui.client.mvc.View; 6. import com.extjs.gxt.ui.client.widget.Viewport; 7. import com.extjs.gxt.ui.client.widget.custom.Portal; 8. import com.extjs.gxt.ui.client.widget.layout.FitLayout; 9. import com.google.gwt.user.client.ui.RootPanel; 10. 11. public class PortalView extends View { 12. private final Portal portal = new Portal(2); 13. 14. public PortalView(PortalController controller) { 15. super(controller); 16. } 17. 18. @Override 19. protected void handleEvent(AppEvent event) { 20. 21. } 22. 23. @Override 24. protected void initialize() { 25. portal.setColumnWidth(0, 0.3); 26. portal.setColumnWidth(1, 0.7); 27. final Viewport viewport = new Viewport(); 28. viewport.setLayout(new FitLayout()); 29. viewport.add(portal); 30. RootPanel.get().add(viewport); 31. } 32. }  当然 handleEvent()方法要处理传入的 NewPortletCreated 事件。但是就现在 而言,我们先不做处理。 [java] view plaincopyprint? 1. @Override 2. protected void handleEvent(AppEvent event) { 3. EventType eventType = event.getType(); 4. if (eventType.equals(AppEvents.NewPortletCreated)) { 5. } 6. }  最后,来到 RSSReader 类的 onModuleLoad 方法——去掉之前所有的代码, 修改后的内码如下: [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client; 2. 3. import com.danielvaughan.rssreader.client.mvc.controllers.AppController; 4. import com.danielvaughan.rssreader.client.mvc.controllers.FeedController; 5. import com.danielvaughan.rssreader.client.mvc.controllers.ItemController; 6. import com.danielvaughan.rssreader.client.mvc.controllers.NavController; 7. import com.danielvaughan.rssreader.client.mvc.controllers.PortalController; 8. import com.danielvaughan.rssreader.client.mvc.controllers.StatusController; 9. import com.danielvaughan.rssreader.client.mvc.events.AppEvents; 10. import com.danielvaughan.rssreader.client.services.FeedService; 11. import com.danielvaughan.rssreader.client.services.FeedServiceAsync; 12. import com.extjs.gxt.ui.client.Registry; 13. import com.extjs.gxt.ui.client.mvc.Dispatcher; 14. import com.google.gwt.core.client.EntryPoint; 15. import com.google.gwt.core.client.GWT; 16. 17. /** 18. * Entry point classes define onModuleLoad(). 19. */ 20. public class RSSReader implements EntryPoint { 21. 22. /** 23. * This is the entry point method. 24. */ 25. @Override 26. public void onModuleLoad() { 27. // final FeedServiceAsync feedService = GWT.create(FeedService.class); 28. // Registry.register(RSSReaderConstants.FEED_SERVICE, feedService); 29. // Dispatcher dispatcher = Dispatcher.get(); 30. // 31. // dispatcher.addController(new AppController()); 32. // dispatcher.addController(new NavController()); 33. // dispatcher.addController(new FeedController()); 34. // dispatcher.addController(new ItemController()); 35. // 36. // dispatcher.addController(new StatusController()); 37. // 38. // dispatcher.dispatch(AppEvents.Init); 39. // dispatcher.dispatch(AppEvents.UIReady); 40. 41. final FeedServiceAsync feedService = GWT.create(FeedService.class); 42. Registry.register(RSSReaderConstants.FEED_SERVICE, feedService); 43. Dispatcher dispatcher = Dispatcher.get(); 44. dispatcher.addController(new PortalController()); 45. } 46. } GXT 之旅:第八章:Portal&Drag-Drop——项目使用 Portal 重构(2) 分类: ExtGWT2012-04-23 15:05223 人阅读评论(0)收藏举报 实际上,Portlet components 并不是那么复杂,大多数的工作,我们在上一节都完成了, Portlet 其实只是一个包装。  新建包:com.danielvaughan.rssreader.client.portlets。在此包下,新建类 NavPortlet extends Portlet(负责左侧导航区域) [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.portlets; 2. 3. import com.extjs.gxt.ui.client.widget.custom.Portlet; 4. import com.extjs.gxt.ui.client.widget.layout.FitLayout; 5. 6. public class NavPortlet extends Portlet { 7. public NavPortlet() { 8. setHeading("Navigation"); 9. setLayout(new FitLayout()); 10. setHeight(610); 11. } 12. }  在 RSSReaderConstants 类里,定义一个新的恒量 NAV_PORTLET [java] view plaincopyprint? 1. public static final String NAV_PORTLET = "navPortlet";  恒量设置之后,在 NavPortlet 类构造函数里,用其来设置 ID [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.portlets; 2. 3. import com.danielvaughan.rssreader.client.RSSReaderConstants; 4. import com.extjs.gxt.ui.client.widget.custom.Portlet; 5. import com.extjs.gxt.ui.client.widget.layout.FitLayout; 6. 7. public class NavPortlet extends Portlet { 8. public NavPortlet() { 9. setHeading("Navigation"); 10. setLayout(new FitLayout()); 11. setHeight(610); 12. 13. setId(RSSReaderConstants.NAV_PORTLET); 14. } 15. }  继续在此构造函数里,新建 NavPanel,加入到 NavPortlet 里。 [java] view plaincopyprint? 1. public NavPortlet() { 2. setHeading("Navigation"); 3. setLayout(new FitLayout()); 4. setHeight(610); 5. 6. setId(RSSReaderConstants.NAV_PORTLET); 7. 8. NavPanel navPanel = new NavPanel(); 9. navPanel.setHeaderVisible(false); 10. add(navPanel); 11. }  NavPortlet 的创建过程已经完毕,接下来需要通知 portal,这一过程——通过 派发 NewPortletCreated 事件,实现通知的过程。 [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.portlets; 2. 3. import com.danielvaughan.rssreader.client.RSSReaderConstants; 4. import com.danielvaughan.rssreader.client.components.NavPanel; 5. import com.danielvaughan.rssreader.client.mvc.events.AppEvents; 6. import com.extjs.gxt.ui.client.mvc.Dispatcher; 7. import com.extjs.gxt.ui.client.widget.custom.Portlet; 8. import com.extjs.gxt.ui.client.widget.layout.FitLayout; 9. 10. public class NavPortlet extends Portlet { 11. public NavPortlet() { 12. setHeading("Navigation"); 13. setLayout(new FitLayout()); 14. setHeight(610); 15. 16. setId(RSSReaderConstants.NAV_PORTLET); 17. 18. NavPanel navPanel = new NavPanel(); 19. navPanel.setHeaderVisible(false); 20. add(navPanel); 21. 22. Dispatcher.forwardEvent(AppEvents.NewPortletCreated , this); 23. } 24. }  NewPortletCreated 事件派发出来之后,我们就需要在对应的 View 类里处理 该事件——编辑 PortalView 类,新建 onNewPortletCreated()方法,根据 RSSReaderConstants.NAV_PORTLET 来判断将传入的 portlet 加入到哪一 列上。 [java] view plaincopyprint? 1. @Override 2. protected void handleEvent(AppEvent event) { 3. EventType eventType = event.getType(); 4. if (eventType.equals(AppEvents.NewPortletCreated)) { 5. onNewPortletCreated(event); 6. } 7. } 8. 9. private void onNewPortletCreated(AppEvent event) { 10. final Portlet portlet = (Portlet) event.getData(); 11. if (portlet.getId() == RSSReaderConstants.NAV_PORTLET) { 12. portal.add(portlet, 0); 13. } else { 14. portal.add(portlet, 1); 15. } 16. }  目前我们所要做的就是,回到 RSSReader.onModuleLoad 方法,新建一个 NavPortlet 实例。剩下的操作,就交给 MVC 自动处理了~~~ [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client; 2. 3. import com.danielvaughan.rssreader.client.mvc.controllers.PortalController; 4. import com.danielvaughan.rssreader.client.portlets.NavPortlet; 5. import com.danielvaughan.rssreader.client.services.FeedService; 6. import com.danielvaughan.rssreader.client.services.FeedServiceAsync; 7. import com.extjs.gxt.ui.client.Registry; 8. import com.extjs.gxt.ui.client.mvc.Dispatcher; 9. import com.google.gwt.core.client.EntryPoint; 10. import com.google.gwt.core.client.GWT; 11. 12. /** 13. * Entry point classes define onModuleLoad(). 14. */ 15. public class RSSReader implements EntryPoint { 16. 17. /** 18. * This is the entry point method. 19. */ 20. @Override 21. public void onModuleLoad() { 22. 23. final FeedServiceAsync feedService = GWT.create(FeedService.class); 24. Registry.register(RSSReaderConstants.FEED_SERVICE, feedService); 25. Dispatcher dispatcher = Dispatcher.get(); 26. dispatcher.addController(new PortalController()); 27. 28. new NavPortlet(); 29. } 30. }  最后,让我们来看看效果吧 GXT 之旅:第八章:Portal&Drag-Drop——项目使用 Portal 重构(3) 分类: ExtGWT2012-04-23 15:49237 人阅读评论(0)收藏举报 按照上一节的思路,我们来继续重构 feed 和 item 区域  新建两个恒量,在 RSSReaderConstants 类里,加入两个新的恒量 ——FEED_PORTLET 和 ITEM_PORTLET [java] view plaincopyprint? 1. public static final String FEED_PORTLET = "feedPortlet"; 2. public static final String ITEM_PORTLET = "itemPortlet";  在包 com.danielvaughan.rssreader.client.portlets:新建 FeedPortlet extends Portlet;并且在构造函数中用相同于 NavPortlet 的代码。 [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.portlets; 2. 3. import com.danielvaughan.rssreader.client.RSSReaderConstants; 4. import com.extjs.gxt.ui.client.widget.custom.Portlet; 5. import com.extjs.gxt.ui.client.widget.layout.FitLayout; 6. 7. public class FeedPortlet extends Portlet { 8. public FeedPortlet() { 9. setHeading("Feed"); 10. setLayout(new FitLayout()); 11. setHeight(350); 12. setId(RSSReaderConstants.FEED_PORTLET); 13. } 14. }  同样的,在 FeedPortlet 类里,新建 FeedPanel 属性,在其构造函数里,设 置头部可见性,并将 FeedPanel 加入到 FeedPortlet 里;最后,派发 NewPortletCreated 事件 [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.portlets; 2. 3. import com.danielvaughan.rssreader.client.RSSReaderConstants; 4. import com.danielvaughan.rssreader.client.components.FeedPanel; 5. import com.danielvaughan.rssreader.client.mvc.events.AppEvents; 6. import com.extjs.gxt.ui.client.mvc.Dispatcher; 7. import com.extjs.gxt.ui.client.widget.custom.Portlet; 8. import com.extjs.gxt.ui.client.widget.layout.FitLayout; 9. 10. public class FeedPortlet extends Portlet { 11. private final FeedPanel feedPanel = new FeedPanel(); 12. 13. public FeedPortlet() { 14. setHeading("Feed"); 15. setLayout(new FitLayout()); 16. setHeight(350); 17. setId(RSSReaderConstants.FEED_PORTLET); 18. 19. feedPanel.setHeaderVisible(false); 20. add(feedPanel); 21. 22. Dispatcher.forwardEvent(AppEvents.NewPortletCreated, this); 23. } 24. }  在包 com.danielvaughan.rssreader.client.portlets:新建 ItemPortlet extends Portlet;并且在构造函数中用相同于 NavPortlet 的代码。 [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.portlets; 2. 3. import com.danielvaughan.rssreader.client.RSSReaderConstants; 4. import com.danielvaughan.rssreader.client.components.ItemPanel; 5. import com.danielvaughan.rssreader.client.mvc.events.AppEvents; 6. import com.extjs.gxt.ui.client.mvc.Dispatcher; 7. import com.extjs.gxt.ui.client.widget.custom.Portlet; 8. import com.extjs.gxt.ui.client.widget.layout.FitLayout; 9. 10. public class ItemPortlet extends Portlet { 11. public ItemPortlet() { 12. setHeading("Item"); 13. setLayout(new FitLayout()); 14. setHeight(250); 15. setId(RSSReaderConstants.ITEM_PORTLET); 16. final ItemPanel itemPanel = new ItemPanel(); 17. itemPanel.setHeaderVisible(false); 18. add(itemPanel); 19. Dispatcher.forwardEvent(AppEvents.NewPortletCreated, this); 20. } 21. }  新建 FeedPortlet 和 ItemPortlet 好之后,我们在 RSSReader.onModuleLoad() 方法里面新建各自的实例 [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client; 2. 3. import com.danielvaughan.rssreader.client.mvc.controllers.PortalController; 4. import com.danielvaughan.rssreader.client.portlets.FeedPortlet; 5. import com.danielvaughan.rssreader.client.portlets.ItemPortlet; 6. import com.danielvaughan.rssreader.client.portlets.NavPortlet; 7. import com.danielvaughan.rssreader.client.services.FeedService; 8. import com.danielvaughan.rssreader.client.services.FeedServiceAsync; 9. import com.extjs.gxt.ui.client.Registry; 10. import com.extjs.gxt.ui.client.mvc.Dispatcher; 11. import com.google.gwt.core.client.EntryPoint; 12. import com.google.gwt.core.client.GWT; 13. 14. /** 15. * Entry point classes define onModuleLoad(). 16. */ 17. public class RSSReader implements EntryPoint { 18. 19. /** 20. * This is the entry point method. 21. */ 22. @Override 23. public void onModuleLoad() { 24. 25. final FeedServiceAsync feedService = GWT.create(FeedService.class); 26. Registry.register(RSSReaderConstants.FEED_SERVICE, feedService); 27. Dispatcher dispatcher = Dispatcher.get(); 28. dispatcher.addController(new PortalController()); 29. 30. new NavPortlet(); 31. new FeedPortlet(); 32. new ItemPortlet(); 33. } 34. }  最后,运行效果如下 GXT 之旅:第九章:Charts 图表——准备工作 分类: ExtGWT2012-05-09 14:37306 人阅读评论(1)收藏举报 第九章:Charts 本章我们要了解 GXT 的 Chart 功能。我们会全面了解 Chart。为了消除对 Chart 功能的疑 惑,我们会使用真实的数据展示 Chart。 我们会涉及到如下 GXt 功能集  Chart  ChartModel  ChartConfig  BarChart  CylinderBarChart  FilledBarChart  SketchBarChart  HorizontalBarChart  PieChart  LineChart  AreaChart Chart 有些不同于 GXT 其他的功能集,与其说他是 GXT framework 的另一个核心部分,不 如说他是“Open Flash Charts 2”被加入到 GXT 当中的。关于具体的“Open Flash Charts 2” 内容可以参考 http://teethgrinder.co.uk/open-flash-chart-2/ 因此 GXT 的 Chart 功能,在使用上需要有一些配置步骤,这些步骤并不是那么显而易见的, 这也就意味着我们在使用 Chart 的时候很容易遇上麻烦。因此,在我们开始之前,我们先按 照基本步骤建立几个 example 程序,先熟悉熟悉。同时让我们了解如果少了几个步骤会显 示什么样子的出错信息,有助于解决以后搭建自己程序时遇到的问题。 导入 chart module  来到我们 RSSReader 项目的 module 配置文件——RSSReader.gwt.xml,导 入有关 chart 的 module [html] view plaincopyprint? 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23.  如果你缺少这一步,当你使用 Chart 组件的时候,会有如下 error message: [java] view plaincopyprint? 1. [ERROR] [rssreader] Line 26: No source code is 2. available for type com.extjs.gxt.charts.client.Chart; did you 3. forget to inherit a required module? 导入 chart resource 就 chart 自身而言,其内容是由 flash 和 JavaScript 组成的。代码里面不单单需要引用 chart module 还要引用 resource 文件。这些 resource 文件需要,我们导入到项目里面。  找到 GXT 发布包里面的 gxt-2.2.5\resources\chart\下所有内容,复制到项目 的 WebContent\gxt\下。  如果上述文件忘记以用的话,会有如下 error message。 [java] view plaincopyprint? 1. [WARN] 404 - GET /gxt/chart/open-flash-chart.swf (127.0.0.1) 1416 2. bytes  同样的 gxt-2.2.5\resources\flash\下所有内容,复制到项目的 WebContent\gxt\下。  如果上述文件忘记以用的话,会有如下 error message。 [java] view plaincopyprint? 1. 18:27:08.015 [ERROR] [rssreader] Unable to load module entry point 2. class 3. com.danielvaughan.rssreader.client.RSSReader (see associated exception for details) 4. com.google.gwt.core.client.JavaScriptException: (TypeError): 5. Cannot call method 'embedSWF' of undefined 6. stack: TypeError: Cannot call method 'embedSWF' of undefined 7. as well as the following message on the Java console: 8. [WARN] 404 - GET /gxt/flash/swfobject.js (127.0.0.1) 1408 bytes  (当然,先前我都直接复制过来了,^_^ ) 加载 chart 所用的 JavaScript 库  其实大家也应该知道,页面引用 flash 文件,离不开 swfobject.js。编辑 RSSReader.html,如下 [java] view plaincopyprint? 1. 2. 3. 4. 5. 6. 7. 8. 10. RSSReader 11. 12. 13.
    14.
    15. RSS Reader
    Loading... 17.
    18.
    19. 21. 22.  如果 swfobject.js 没有引入的话,控制台会报错的哦 [java] view plaincopyprint? 1. [ERROR] [rssreader] Unable to load module entry point 2. class 3. com.danielvaughan.rssreader.client.RSSReader (see associated 4. exception for 5. details) 6. com.google.gwt.core.client.JavaScriptException: (TypeError): 7. Cannot call 8. method 'embedSWF' of undefined 9. stack: TypeError: Cannot call method 'embedSWF' of undefined: GXT 之旅:第九章:Charts 图表——Chart 的基本创建(1) 分类: ExtGWT2012-05-09 17:06318 人阅读评论(0)收藏举报 Chart class 作为一个 java 类,Chart 其实就是针对 Open Flash Chart 库做了包装,并且让用户在使用 起来就像其他的 GXT 的 components 一样。 需要注意的是,在使用 Chart 的时候,需要给定一个 open-flash-chart.swf 的 URL 地址—— 上一节,我们拷入到 RSSReader 项目里面的地址。当然 URL 必须有效,否则 Chart 不会 被渲染出来,除了控制室台 404error 之外,页面上是没有什么错误的。 比如如下的 URL 是有效的: [java] view plaincopyprint? 1. Chart chart = new Chart("gxt/chart/open-flash-chart.swf"); 因此,如下 URL 是无效的: [java] view plaincopyprint? 1. Chart chart = new Chart("wrong/path/open-flash-chart.swf"); 那么就会有如下的 error message [java] view plaincopyprint? 1. [WARN] 404 - GET /wrong/path/open-flash-chart.swf (127.0.0.1) 1417 2. bytes 接下来,让我们新建一个 Portlet 来展示 Chart  新建 package:com.danielvaughan.rssreader.client.charts——在此 package 下,新建一个 FeedChart 容器,extends LayoutContainer。 [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.charts; 2. 3. import com.extjs.gxt.ui.client.widget.LayoutContainer; 4. 5. public class FeedChart extends LayoutContainer { 6. 7. }  新建属性——chart,注意传入的 URL [java] view plaincopyprint? 1. private final Chart chart = new Chart("gxt/chart/open-flash-chart.swf");  Override onRender(),具体实现如下 [java] view plaincopyprint? 1. @Override 2. protected void onRender(Element parent, int index) { 3. super.onRender(parent, index); 4. setLayout(new FitLayout()); 5. chart.setBorders(true); 6. add(chart); 7. }  进入 package:com.danielvaughan.rssreader.client.portlets,为 FeedChart 新建一个 ChartPortlet [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.portlets; 2. 3. import com.extjs.gxt.ui.client.widget.custom.Portlet; 4. 5. public class ChartPortlet extends Portlet { 6. 7. }  在 ChartPortlet 类,新建属性 feedChart [java] view plaincopyprint? 1. private final FeedChart feedChart = new FeedChart();  构造函数 ChartPortlet()里定义一些基本属性: [java] view plaincopyprint? 1. public ChartPortlet() { 2. setHeading("Chart"); 3. setId(RSSReaderConstants.CHART_PORTLET); 4. setLayout(new FitLayout()); 5. setHeight(250); 6. }  当然对于恒量 CHART_PORTLET,定义: [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client; 2. 3. public class RSSReaderConstants { 4. public static final String FEED_SERVICE = "feedService"; 5. public static final String FEED_STORE = "feedStore"; 6. public static final String NAV_PORTLET = "navPortlet"; 7. public static final String FEED_PORTLET = "feedPortlet"; 8. public static final String ITEM_PORTLET = "itemPortlet"; 9. public static final String FEED_DD_GROUP = "feedDDGroup"; 10. public static final String ITEM_DD_GROUP = "itemDDGroup"; 11. public static final String OVERVIEW_PORTLET = "overviewPortlet"; 12. 13. public static final String CHART_PORTLET = "chartPortlet"; 14. }  回到,ChartPortlet 构造函数。将 feedChart 加入到 Portlet 里这一完毕过程, 派发出去,并且传入自己(this) [java] view plaincopyprint? 1. public ChartPortlet() { 2. setHeading("Chart"); 3. setId(RSSReaderConstants.CHART_PORTLET); 4. setLayout(new FitLayout()); 5. setHeight(250); 6. 7. Dispatcher.forwardEvent(AppEvents.NewPortletCreated, this); 8. }  我们想要让,ChartPortlet 显示在 Portal 里面的第一列。所以我们需要修改 PortalView.onAddPortlet()方法。 [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.mvc.views; 2. 3. import com.danielvaughan.rssreader.client.RSSReaderConstants; 4. import com.danielvaughan.rssreader.client.mvc.controllers.PortalController; 5. import com.danielvaughan.rssreader.client.mvc.events.AppEvents; 6. import com.extjs.gxt.ui.client.event.EventType; 7. import com.extjs.gxt.ui.client.mvc.AppEvent; 8. import com.extjs.gxt.ui.client.mvc.View; 9. import com.extjs.gxt.ui.client.widget.Viewport; 10. import com.extjs.gxt.ui.client.widget.custom.Portal; 11. import com.extjs.gxt.ui.client.widget.custom.Portlet; 12. import com.extjs.gxt.ui.client.widget.layout.FitLayout; 13. import com.google.gwt.user.client.ui.RootPanel; 14. 15. public class PortalView extends View { 16. private final Portal portal = new Portal(2); 17. 18. public PortalView(PortalController controller) { 19. super(controller); 20. } 21. 22. @Override 23. protected void handleEvent(AppEvent event) { 24. EventType eventType = event.getType(); 25. if (eventType.equals(AppEvents.NewPortletCreated)) { 26. onNewPortletCreated(event); 27. } 28. } 29. 30. private void onNewPortletCreated(AppEvent event) { 31. final Portlet portlet = (Portlet) event.getData(); 32. if (portlet.getId() == RSSReaderConstants.NAV_PORTLET 33. || portlet.getId() == RSSReaderConstants.CHART_PORTLET) { //here 34. portal.add(portlet, 0); 35. } else { 36. portal.add(portlet, 1); 37. } 38. } 39. 40. @Override 41. protected void initialize() { 42. portal.setColumnWidth(0, 0.3); 43. portal.setColumnWidth(1, 0.7); 44. final Viewport viewport = new Viewport(); 45. viewport.setLayout(new FitLayout()); 46. viewport.add(portal); 47. RootPanel.get().add(viewport); 48. } 49. }  因为 NavPortlet 和 ChartPortlet 都要添加到第一列,所以我们要缩短 NavPortlet 的高度。 [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.portlets; 2. 3. import com.danielvaughan.rssreader.client.RSSReaderConstants; 4. import com.danielvaughan.rssreader.client.components.NavPanel; 5. import com.danielvaughan.rssreader.client.mvc.events.AppEvents; 6. import com.extjs.gxt.ui.client.mvc.Dispatcher; 7. import com.extjs.gxt.ui.client.widget.custom.Portlet; 8. import com.extjs.gxt.ui.client.widget.layout.FitLayout; 9. 10. public class NavPortlet extends Portlet { 11. public NavPortlet() { 12. setHeading("Navigation"); 13. setLayout(new FitLayout()); 14. setHeight(350); // here 15. 16. setId(RSSReaderConstants.NAV_PORTLET); 17. 18. NavPanel navPanel = new NavPanel(); 19. navPanel.setHeaderVisible(false); 20. add(navPanel); 21. 22. Dispatcher.forwardEvent(AppEvents.NewPortletCreated , this); 23. } 24. }  最后,在程序入口方法 RSSReader.onModuleLoad(),新建 ChartPortlet [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client; 2. 3. import com.danielvaughan.rssreader.client.mvc.controllers.PortalController; 4. import com.danielvaughan.rssreader.client.portlets.ChartPortlet; 5. import com.danielvaughan.rssreader.client.portlets.FeedPortlet; 6. import com.danielvaughan.rssreader.client.portlets.ItemPortlet; 7. import com.danielvaughan.rssreader.client.portlets.NavPortlet; 8. import com.danielvaughan.rssreader.client.portlets.OverviewPortlet; 9. import com.danielvaughan.rssreader.client.services.FeedService; 10. import com.danielvaughan.rssreader.client.services.FeedServiceAsync; 11. import com.extjs.gxt.ui.client.Registry; 12. import com.extjs.gxt.ui.client.mvc.Dispatcher; 13. import com.google.gwt.core.client.EntryPoint; 14. import com.google.gwt.core.client.GWT; 15. 16. /** 17. * Entry point classes define onModuleLoad(). 18. */ 19. public class RSSReader implements EntryPoint { 20. 21. /** 22. * This is the entry point method. 23. */ 24. @Override 25. public void onModuleLoad() { 26. final FeedServiceAsync feedService = GWT.create(FeedService.class); 27. Registry.register(RSSReaderConstants.FEED_SERVICE, feedService); 28. Dispatcher dispatcher = Dispatcher.get(); 29. dispatcher.addController(new PortalController()); 30. new NavPortlet(); 31. new OverviewPortlet(); 32. new FeedPortlet(); 33. new ItemPortlet(); 34. 35. new ChartPortlet();// here 36. } 37. }  此时,运行我们的 RSSReader,会发现:ChartPortlet 能够显示出来,但是 会在页面上有如下错误的显示效果。(低版本下,会因为没有数据而产生错 误,chart.setVisible =false 既可解决。但是我现在的版本没有问题: GXT-2.2.5)  正常的显示效果如下: GXT 之旅:第九章:Charts 图表——BarChart(2) 分类: ExtGWT2012-05-11 14:09246 人阅读评论(0)收藏举报 ChartModel class chartModel extends BaseModel,为 "Open Flash Chart" chart 的数据源所用。 ChartModuel 用来定义类型和数据,他会包含一个 or 多个 ChartConfig 对象用来定义各种 类型的 Chart。 ChartConfig class ChartConfig extends BaseModel 但是一个抽象类。他作为一个基类,为其子类服务。其子 类则定义了具体的 Chart 类型,并且提供一个层级关系的各类型的 chart style 下面的图表,显示了不同类型的 Chart 之间关系,但是他们都 extends ChartConfig BarChart class 我们首先了解的是 BarChart。以最简单的形式,我们来使用她,会有如下步骤:  新建一个 createChartModelData()方法  在方法内,新建 ChartModel  新建 BarChart ChartConfig  给 BarChart 加入一些 values  将 BarChart 加入到 ChartModel  方法最后返回的是 ChartModel [java] view plaincopyprint? 1. private ChartModel createChartModel() { 2. 3. ChartModel chartModel = new ChartModel(); 4. BarChart chartConfig = new BarChart(); 5. chartConfig.addValues(6936,8628,41832,68376,296,10114,4693); 6. chartModel.addChartConfig(chartConfig); 7. return chartModel; 8. 9. } 但是现在的问题是,我们给予的 values 太大了远远超过 Y 轴,那么我们重新整理一下。新 建一个 getChartModel()方法 [java] view plaincopyprint? 1. private ChartModel getChartModel() { 2. ChartModel chartModel = new ChartModel( 3. "Population of Western European Countries in 1950 (000's)", 4. "fontsize:14px;color:#000000");//标题 5. chartModel.setBackgroundColour("#ffffff"); 6. 7. XAxis xAxis = new XAxis();//x 轴 8. xAxis.addLabels("Austria", "Belgium", "France", "Germany", 9. "Luxembourg", "Netherlands", "Switzerland"); 10. chartModel.setXAxis(xAxis); 11. 12. YAxis yAxis = new YAxis();//y 轴 13. yAxis.setRange(0, 70000, 10000); 14. chartModel.setYAxis(yAxis); 15. 16. BarChart chartConfig = new BarChart(); 17. chartConfig.addValues(6936, 8628, 41832, 68376, 296, 10114, 4693); 18. chartModel.addChartConfig(chartConfig); 19. 20. return chartModel; 21. }  通过 ChartModel 构造函数传入标题,并且指定了标题的 css。  设置背景颜色  设置 X 轴的显示标签  设置 Y 轴的显示标签 经过这一番折腾之后,chart 看起来满意多了。将鼠标放置在显示条上,会有实际的值显示 出来。 默认的 BarChart 显示的效果就是如上图了,我们可以通过 BarStyle 提供的参数传入到 BarChart 构造函数里。 [java] view plaincopyprint? 1. BarChart chartConfig = new BarChart(BarStyle.THREED); //立体显示 [java] view plaincopyprint? 1. BarChart chartConfig = new BarChart(BarStyle.GLASS); 这些仅仅是个开始,我们以一种最简单的使用方式了解他们。其实我也了解的不多,哈哈 GXT 之旅:第九章:Charts 图表——各种 Chart(3) 分类: ExtGWT2012-05-14 17:32455 人阅读评论(0)收藏举报 CylinderBarChart class [java] view plaincopyprint? 1. BarChart chartConfig = new CylinderBarChart(); 2. chartConfig.addValues(6936,8628,41832,68376,296,10114,4693); FilledBarChart class FilledBarChart 看起来和标准的 BarChart 没什么两样。但是她可以通过 setOutlineColor() 方法设置圆柱的轮廓颜色。 [java] view plaincopyprint? 1. FilledBarChart chartConfig = new FilledBarChart(); 2. chartConfig.setColour("#cc0000"); 3. chartConfig.setOutlineColour("#660000"); 4. chartConfig.addValues(6936,8628,41832,68376,296,10114,4693); SketchBarChart class 如下图,是一个简单的 SketchBarChart 的实例图片。 [java] view plaincopyprint? 1. BarChart chartConfig = new SketchBarChart(); 2. chartConfig.addValues(6936,8628,41832,68376,296,10114,4693); 内部类 BarChart.Bar class 除了简单的创建 BarChart 之外呢,BarChart class 内部还有一个 Bar class,可以让我们更 具体的定义每一个 bar 的显示。 如下,我们可以为每个 bar 定义不同的颜色 [java] view plaincopyprint? 1. BarChart chartConfig = new SketchBarChart(); 2. chartConfig.addBars(new BarChart.Bar(6936, "#FF0000")); 3. chartConfig.addBars(new BarChart.Bar(8628, "#FFA500")); 4. chartConfig.addBars(new BarChart.Bar(41832, "#FFFF00")); 5. chartConfig.addBars(new BarChart.Bar(68376,"#008000")); 6. chartConfig.addBars(new BarChart.Bar(296, "#0000FF")); 7. chartConfig.addBars(new BarChart.Bar(10114,"#4B0082")); 8. chartConfig.addBars(new BarChart.Bar(4693, "#EE82EE")); HorizontalBarChart class HorizontalBarChart 工作方式和标准的 BarChart 一样。当然,顾明思议,就是 Y 轴编程和 X 轴交换位置。值得注意的是现在 Y 轴的标签(国家)和 X 轴的 values 是相反序的! [java] view plaincopyprint? 1. YAxis yAxis = new YAxis(); 2. yAxis.addLabels("Switzerland","Netherlands","Luxembourg","Germany","Fr ance","Belgium","Austria"); 3. yAxis.setOffset(true); 4. chartModel.setYAxis(yAxis); 5. 6. XAxis xAxis = new XAxis(); 7. xAxis.setRange(0, 70000, 10000); 8. chartModel.setXAxis(xAxis); 9. 10. HorizontalBarChart chartConfig = new HorizontalBarChart(); 11. chartConfig.addValues(6936,8628,41832,68376,296,10114,4693); PieBarChart class 下面,让我们把视线从多样的 BarChart 转移到其他的 Chart 上。事实上,BarChart 和 PieChart 之间的不同就是体现在 ChartConfig 的定义上。 让我们继续,通过定义一系列的颜色来区分开每个区域。 [java] view plaincopyprint? 1. private ChartModel getChartModel() { 2. ChartModel chartModel = new ChartModel(); 3. PieChart chartConfig = new PieChart(); 4. chartConfig.setColours("#FF0000", "#FFA500", "#FFFF00", "#008000", 5. "#0000FF", "#4B0082", "#EE82EE"); 6. chartConfig.addValues(6936, 8628, 41832, 68376, 296, 10114, 4693); 7. chartModel.addChartConfig(chartConfig); 8. return chartModel; 9. } 当然,目前的图表看上去,还不是那么实用,因为每个切片上还没有标签。 PieChart.Slice class 正如 BarChart.Bar 一样,PieChart.Slice 是定义 PieChart 中每一个切片的——定义 value 和 label [java] view plaincopyprint? 1. PieChart chartConfig = new PieChart(); 2. 3. chartConfig.setColours("#FF0000", "#FFA500", "#FFFF00", "#008000","#0000FF","#4B0082", "#EE82EE"); 4. chartConfig.addSlices(new PieChart.Slice(6936,"Austria")); 5. chartConfig.addSlices(new PieChart.Slice(8628,"Belgium")); 6. chartConfig.addSlices(new PieChart.Slice(41832,"France")); 7. chartConfig.addSlices(new PieChart.Slice(68376,"Germany")); 8. chartConfig.addSlices(new PieChart.Slice(296,"Luxembourg")); 9. chartConfig.addSlices(new PieChart.Slice(10114,"Netherlands")); 10. chartConfig.addSlices(new PieChart.Slice(4693,"Switzerland")); 11. 12. chartModel.addChartConfig(chartConfig); LineChart LineChart 的使用方式都是类似的: [java] view plaincopyprint? 1. private ChartModel getChartModel() { 2. //model 3. ChartModel chartModel = new ChartModel("Population of Germany", 4. "font-size:14px;color:#000000"); 5. chartModel.setBackgroundColour("#ffffff"); 6. XAxis xAxis = new XAxis(); 7. xAxis.addLabels("1950", "1960", "1970", "1980", "1990", "2000"); 8. chartModel.setXAxis(xAxis); 9. YAxis yAxis = new YAxis(); 10. yAxis.setRange(50000, 100000, 10000); 11. yAxis.setOffset(true); 12. chartModel.setYAxis(yAxis); 13. //config 14. LineChart chartConfig = new LineChart(); 15. chartConfig.addValues(68376, 72815, 78169, 78289, 79433, 82075); 16. chartConfig.setText("Germany");//当前 line 的名称 17. //model+config 18. chartModel.addChartConfig(chartConfig); 19. return chartModel; 20. } 如果想再次加入一条 line,其实很简单——需要再定义一个 ChartConfig 既可。让后将 ChartConfig 加入到 model 里。 [java] view plaincopyprint? 1. LineChart germanyChartConfig = new LineChart(); 2. germanyChartConfig.addValues(68376,72815,78169,78289,79433,82075); 3. germanyChartConfig.setColour("#ff0000"); 4. germanyChartConfig.setText("Germany"); 5. 6. chartModel.addChartConfig(germanyChartConfig); 7. 8. LineChart franceChartConfig = new LineChart(); 9. franceChartConfig.addValues(41832,45674,50771,53950,56842,59128); 10. franceChartConfig.setColour("#000066"); 11. franceChartConfig.setText("France"); 12. 13. chartModel.addChartConfig(franceChartConfig); 当一个 model 里,拥有多组数据(config)的时候,不是所有的 config 必须都是同一种类 型。可以让一个 Linechart 和 BarChart 同时显示在一起。例如: [java] view plaincopyprint? 1. BarChart germanyChartConfig = new BarChart(); 2. germanyChartConfig.addValues(68376,72815,78169,78289,79433,82075); 3. germanyChartConfig.setColour("#ff0000"); 4. germanyChartConfig.setText("Germany"); 5. 6. chartModel.addChartConfig(germanyChartConfig); 7. 8. LineChart franceChartConfig = new LineChart(); 9. franceChartConfig.addValues(41832,45674,50771,53950,56842,59128); 10. franceChartConfig.setColour("#000066"); 11. franceChartConfig.setText("France"); 12. 13. chartModel.addChartConfig(franceChartConfig); GXT 之旅:第九章:Charts 图表——各种 Chart(4) 分类: ExtGWT2012-05-24 12:04246 人阅读评论(1)收藏举报 AreaChart class AreaChart extends LineChart ,因此工作方式都是相同的。不同之处是替换了单独的丝线, 变成了线性区域。 [java] view plaincopyprint? 1. AreaChart germanyChartConfig = new AreaChart(); 2. germanyChartConfig.addValues(68376,72815,78169,78289,79433,82075); 3. germanyChartConfig.setColour("#ff0000"); 4. germanyChartConfig.setText("Germany"); 5. 6. AreaChart franceChartConfig = new AreaChart(); 7. franceChartConfig.addValues(41832,45674,50771,53950,56842,59128); 8. franceChartConfig.setColour("#000066"); 9. franceChartConfig.setText("France"); ScatterChart class ScatterChart 是 GXT 中非常有用的 component chart,但 GXT 并不是完全支持她。 比如,我们有如下数据 [java] view plaincopyprint? 1. ScatterChart chartConfig = new ScatterChart(); 2. chartConfig.addPoint(41832, 68376); 3. chartConfig.addPoint(45674, 72815); 4. chartConfig.addPoint(50771, 78169); 5. chartConfig.addPoint(53950, 78289); 6. chartConfig.addPoint(56842, 79433); 7. chartConfig.addPoint(59128, 82075); 数据设置进去了,但是我们没有办法在图表上设置数据的渲染方式——只有在鼠标经过的他 们的时候,才可以显示出来。 使用 PieChart 在我们 RSSReader 项目里面,还没有显示任何一个 Chart 功能。虽然一个小小的 demo 项 目里面还没有什么功能需要用到 chart,但是如果往里面加还是能加进去的 让我们新建一个 chart 显示一周内每天的 feed 信息的发布时间的统计情况。  编辑 com.danielvaughan.rssreader.client.charts.FeedChart,新增如下方法 ——prepareData(),当然这个方法不属于 Chart 本身的部分,但是是做为 Chart 提供数据来源的。 [java] view plaincopyprint? 1. @Override 2. protected void onRender(Element parent, int index) { 3. super.onRender(parent, index); 4. setLayout(new FitLayout()); 5. chart.setBorders(true); 6. chart.setVisible(false);//为了避免报错,所以一开始设置不可见,等有 chartModel 的时候再设置可见 7. add(chart); 8. } 9. 10. private HashMap prepareData(List items) { 11. HashMap days = new HashMap(); 12. for (Item item : items) { 13. DateTimeFormat fmt = DateTimeFormat.getFormat("EEEE"); 14. String day = fmt.format(item.getPubDate()); 15. Integer dayOccurance = days.get(day); 16. if (dayOccurance == null) { 17. days.put(day, 1); 18. } else { 19. days.put(day, ++dayOccurance); 20. } 21. } 22. return days; 23. }  新建方法 createChartModelData(List items) [java] view plaincopyprint? 1. private ChartModel createChartModelData(List items) { 2. // create chartModel 3. ChartModel chartModel = new ChartModel("Posts per week of day", 4. "font-size: 14px; font-family: Verdana; text-align: center;"); 5. chartModel.setBackgroundColour("#ffffff"); 6. // create PieChart 7. PieChart pie = new PieChart(); 8. pie.setColours("#FF0000", "#FFA500", "#FFFF00", "#008000", "#0000FF", 9. "#4B0082", "#EE82EE"); 10. // add Slice, set values 11. HashMap days = prepareData(items); 12. for (String key : days.keySet()) { 13. pie.addSlices(new PieChart.Slice(days.get(key), key)); 14. } 15. chartModel.addChartConfig(pie); 16. 17. return chartModel; 18. }  新建方法 setFeed()——基本实现如下: [java] view plaincopyprint? 1. public void setFeed(final Feed feed) { 2. final FeedServiceAsync feedService = Registry 3. .get(RSSReaderConstants.FEED_SERVICE); 4. //根据 uuid 获得其 items 5. feedService.loadItems(feed.getUuid(), new AsyncCallback>() { 6. @Override 7. public void onFailure(Throwable caught) { 8. Dispatcher.forwardEvent(AppEvents.Error, caught); 9. } 10. 11. @Override 12. public void onSuccess(List items) { 13. //成功之后将 items 转换成 ChartModel,设置到 chart 里。 14. chart.setChartModel(createChartModelData(items)); 15. } 16. }); 17. }  现在 Chart 已经设置了其 ChartModel,那么就不会出现“Open Flash Chart” 的 data error。因此我们可以大胆的设置 chart visible=true [java] view plaincopyprint? 1. public void setFeed(final Feed feed) { 2. final FeedServiceAsync feedService = Registry 3. .get(RSSReaderConstants.FEED_SERVICE); 4. // 根据 uuid 获得其 items 5. feedService.loadItems(feed.getUuid(), new AsyncCallback>() { 6. @Override 7. public void onFailure(Throwable caught) { 8. Dispatcher.forwardEvent(AppEvents.Error, caught); 9. } 10. 11. @Override 12. public void onSuccess(List items) { 13. // 成功之后将 items 转换成 ChartModel,设置到 chart 里。 14. chart.setChartModel(createChartModelData(items)); 15. } 16. }); 17. // 18. if (!chart.isVisible()) { 19. chart.setVisible(true); 20. } 21. }  编辑:com.danielvaughan.rssreader.client.portlets..ChartPortlet——新增一 个 onFeedsDropped() [java] view plaincopyprint? 1. private void onFeedsDropped(DNDEvent event) { 2. // 接收 feeds,将 feed 传入到 feedChart 中去。 3. List beanModels = event.getData(); 4. for (BeanModel beanModel : beanModels) { 5. Feed feed = beanModel.getBean(); 6. feedChart.setFeed(feed); 7. } 8. }  Overwrite onRender()方法。让 ChartPortlet 成为另外一个 DropTarget,并且 和 FeedPortlet 拥有同样的分组 FEED_DD_GROUP [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.portlets; 2. 3. import java.util.List; 4. 5. import com.danielvaughan.rssreader.client.RSSReaderConstants; 6. import com.danielvaughan.rssreader.client.charts.FeedChart; 7. import com.danielvaughan.rssreader.client.mvc.events.AppEvents; 8. import com.danielvaughan.rssreader.shared.model.Feed; 9. import com.extjs.gxt.ui.client.dnd.DND; 10. import com.extjs.gxt.ui.client.dnd.DropTarget; 11. import com.extjs.gxt.ui.client.event.DNDEvent; 12. import com.extjs.gxt.ui.client.mvc.Dispatcher; 13. import com.extjs.gxt.ui.client.widget.custom.Portlet; 14. import com.extjs.gxt.ui.client.widget.layout.FitLayout; 15. import com.google.gwt.user.client.Element; 16. 17. public class ChartPortlet extends Portlet { 18. private final FeedChart feedChart = new FeedChart(); 19. 20. public ChartPortlet() { 21. setHeading("Chart"); 22. setId(RSSReaderConstants.CHART_PORTLET); 23. setLayout(new FitLayout()); 24. setHeight(250); 25. 26. Dispatcher.forwardEvent(AppEvents.NewPortletCreated, this); 27. } 28. 29. private void onFeedsDropped(DNDEvent event) { 30. // 接收 feeds,将 feed 传入到 feedChart 中去。 31. List beanModels = event.getData(); 32. for (BeanModel beanModel : beanModels) { 33. Feed feed = beanModel.getBean(); 34. feedChart.setFeed(feed); 35. } 36. } 37. 38. @Override 39. protected void onRender(Element parent, int index) { 40. super.onRender(parent, index); 41. DropTarget target = new DropTarget(this) { 42. @Override 43. protected void onDragDrop(DNDEvent event) { 44. super.onDragDrop(event); 45. onFeedsDropped(event); 46. } 47. }; 48. target.setOperation(DND.Operation.COPY); 49. target.setGroup(RSSReaderConstants.FEED_DD_GROUP); 50. } 51. }  FeedChart 类的完整代码如下: [java] view plaincopyprint? 1. package com.danielvaughan.rssreader.client.charts; 2. 3. import java.util.HashMap; 4. import java.util.List; 5. 6. import com.danielvaughan.rssreader.client.RSSReaderConstants; 7. import com.danielvaughan.rssreader.client.mvc.events.AppEvents; 8. import com.danielvaughan.rssreader.client.services.FeedServiceAsync; 9. import com.danielvaughan.rssreader.shared.model.Feed; 10. import com.danielvaughan.rssreader.shared.model.Item; 11. import com.extjs.gxt.charts.client.Chart; 12. import com.extjs.gxt.charts.client.model.ChartModel; 13. import com.extjs.gxt.charts.client.model.charts.PieChart; 14. import com.extjs.gxt.ui.client.Registry; 15. import com.extjs.gxt.ui.client.mvc.Dispatcher; 16. import com.extjs.gxt.ui.client.widget.LayoutContainer; 17. import com.extjs.gxt.ui.client.widget.layout.FitLayout; 18. import com.google.gwt.i18n.client.DateTimeFormat; 19. import com.google.gwt.user.client.Element; 20. import com.google.gwt.user.client.rpc.AsyncCallback; 21. 22. public class FeedChart extends LayoutContainer { 23. 24. private final Chart chart = new Chart("gxt/chart/open-flash-chart.swf"); 25. 26. private ChartModel createChartModelData(List items) { 27. ChartModel chartModel = new ChartModel("Posts per week of day", 28. "font-size: 14px; font-family: Verdana; text-align: center;"); 29. chartModel.setBackgroundColour("#ffffff"); 30. PieChart pie = new PieChart(); 31. pie.setColours("#FF0000", "#FFA500", "#FFFF00", "#008000", "#0000FF", 32. "#4B0082", "#EE82EE"); 33. HashMap days = prepareData(items); 34. for (String key : days.keySet()) { 35. pie.addSlices(new PieChart.Slice(days.get(key), key)); 36. } 37. chartModel.addChartConfig(pie); 38. return chartModel; 39. } 40. 41. @Override 42. protected void onRender(Element parent, int index) { 43. super.onRender(parent, index); 44. setLayout(new FitLayout()); 45. chart.setBorders(true); 46. chart.setVisible(false); 47. add(chart); 48. } 49. 50. private HashMap prepareData(List items) { 51. HashMap days = new HashMap(); 52. for (Item item : items) { 53. DateTimeFormat fmt = DateTimeFormat.getFormat("EEEE"); 54. String day = fmt.format(item.getPubDate()); 55. Integer dayOccurance = days.get(day); 56. if (dayOccurance == null) { 57. days.put(day, 1); 58. } else { 59. days.put(day, ++dayOccurance); 60. } 61. } 62. return days; 63. } 64. 65. public void setFeed(final Feed feed) { 66. final FeedServiceAsync feedService = Registry 67. .get(RSSReaderConstants.FEED_SERVICE); 68. feedService.loadItems(feed.getUuid(), new AsyncCallback>() { 69. @Override 70. public void onFailure(Throwable caught) { 71. Dispatcher.forwardEvent(AppEvents.Error, caught); 72. } 73. 74. @Override 75. public void onSuccess(List items) { 76. chart.setChartModel(createChartModelData(items)); 77. } 78. }); 79. if (!chart.isVisible()) { 80. chart.setVisible(true); 81. } 82. } 83. }  最后运行效果如下:
    还剩348页未读

    继续阅读

    下载pdf到电脑,查找使用更方便

    pdf的实际排版效果,会与网站的显示效果略有不同!!

    需要 8 金币 [ 分享pdf获得金币 ] 0 人已下载

    下载pdf

pdf贡献者

aljazeeras

贡献于2013-06-12

下载需要 8 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!