技术开发 频道

基于Jazz技术构建企业级Web2.0应用(4)

《基于Jazz技术构建企业级Web2.0应用》系列第一部分:数据模型设计与持久化

《基于Jazz技术构建企业级Web2.0应用》系列第二部分:服务层设计与实现

 《基于Jazz技术构建企业级Web2.0应用》系列第三部分:利用Jazz Process实现业务流程定制

【IT168 专稿】

    在本系列前三部分中,我们以PetStore为例先后介绍了如何使用Jazz技术开发一个企业级应用的数据模型层和服务层,在这一部分中我们将要介绍如何开发此应用的富客户端UI。

    Jazz开发框架为我们提供了两种富客户端方式供选择,一种是基于Eclipse的RCP,另外一种是目前很流行的Web客户端。Jazz为这两种服客户端都提供良好的基础设施支持,开发人员基于扩展点就可以开发出适合自己工程的客户端程序。本文首先介绍Jazz Web UI Foundation, 然后以Pet Store为例介绍基于Jazz的Web UI开发流程。

Jazz Web UI Foundation

    Jazz的技术实现是基于Eclipse的OSGi包机制之上,每个组件都有良好的模块化设计结构, 具有良好的可扩展性,Web UI也不例外。 下面我们就来看一下Jazz Web UI Foundation的结构。Jazz Web UI Foundation对应四个包:

    · org.dojotoolkit

    · net.jazz.ajax (Jazz Ajax Framework)

    · com.ibm.team.repository.web

    · com.ibm.team.process.web

    它们之间的关系如图1所示:


    图1. Jazz Web UI Foundation 结构图

    目前在业界有许多的Ajax Framework,Jazz采用的是Dojo Toolkit。 Dojo Toolkit在Jazz里被打包成一个命名为org.dojotoolkit的OSGi包, 这样它就可以通过Eclipse的插件机制来发挥作用。其中,Dojo通过提供一系列APIs,为JavaScript提供模块化管理能力和类Java的面向对象编程能力,给我们提供了一种方便的对象创建与扩展机制。同时,Dojo为声明式创建Ajax widgets提供一个强大的机制--它使用简单的HTML模板,并为这些widgets添加统一的事件处理机制,既屏蔽了浏览器对事件处理方法的差别,又使得程序开发人员可以方便的对站点进行处理。

    net.jazz.ajax就是Jazz Ajax Framework(即JAF),是Jazz Web Foundation的核心。它首先将OSGi模块化模型扩展到Ajax领域,通过严格定义的类加载模型,组件开发人员可以获得Eclipse SDK插件开发环境(PDE)工具的强有力支持。同时,OSGi框架允许开发人员定义扩展点和这些扩展点的扩展性,从而很容易的对UI在多个级别上进行扩展。JAF的模块化模型也为Ajax资源的动态优化提供保证,它通过分析每个JAF组件中的元数据和Ajax代码,在执行应用时自动的优化Ajax资源,并能够通过延迟加载机制,智能的决定加载那些必须的模块,既加快页面装载速度,又减少应用程序的内存消耗。另外,JAF中URI驱动框架也可以简化后退、前进和历史标签,在不牺牲这些基本浏览器功能的前提下,使得开发人员很容易的达到构造复杂Ajax应用的目的。开发人员也可以在应用程序的URI中简单的插入"?debug=true"就能启动JAF的调试模式。

    从以上的介绍我们可以看出,在Jazz Web UI Foundation中,Dojo Toolkit贡献了丰富的widget可供UI程序员使用。而net.jazz.ajax提供强大的UI模块化开发能力,这样不同的模块开发人员就可以独立开发自己模块的UI,并且可以使用Eclipse的扩展点机制实现模块与模块之间UI的松耦合。这些对Java EE的项目开发是非常重要的。

    通常UI开发人员除了关心自己所使用的编程框架外还会关心UI和应用后端的数据交互接口,下面我们就介绍一下Jazz的数据封装和解封装,让您熟悉一下UI端请求数据的方式和所消费的数据格式。

Jazz的数据封装和解封装

    Jazz和许多的Java EE项目一样,采用分层模型。Web UI通过调用REST服务来使用数据。数据的传输和转换流程为:

    1.REST服务将数据封装成DTO;

    2.Jazz Ajax Framework服务器端部分将DTO数据封装成SOAP消息发送给客户端;

    3.Jazz Ajax Framework客户端部分将数据从SOAP消息解封装并转换成JSON格式。

    程序员可见并需要编程的有两部分:

    1.在 REST服务中将数据封装成DTO。

    DTO作为一种设计模式在Java EE中被广泛使用, 负责服务器端和客户端之间的交互,REST服务在接到客户端的方法请求之后做一系列的逻辑操作,然后返回DTO形式封装的数据。

    2.在Web UI中使用JSON格式数据。

    Jazz Ajax Framework提供com.ibm.team.repository.web.transport.TeamServerClient来发起Ajax请求,TeamServerClient在得到服务器的响应后将拿到的SOAP消息解封装并将消息体中的values转换成JSON供客户端使用。

Jazz的Web UI开发流程

    下面我们就来介绍PetStore的Web UI组件开发流程。

    创建Web插件

    创建Web插件项目com.ibm.petstore.web,我们将在其中定义Web UI的各个页面并与REST服务交互。需要注意的是在创建插件的Wizard中不要勾选“Create a Java project”。

    下面我们创建与PetStore  Web UI相关的文件夹和文件。图2显示了创建完成的文件夹和文件:


    图2. PetStore  Web UI文件目录

    · resources文件夹是webBundles扩展点将页面内容注册到web服务的缺省目录。

    · ui文件夹是用来存放所有与页面相关的文件。

    · client文件夹存放与REST服务交互的文件。

    · Internal文件夹说明当前目录下没有public API 或者可重用的公用widget。

    · page文件夹存放与子组件对照的web页面。

    另外需要注意的是我们创建Dojo widgets,还需要创建“internal/templates”文件夹,用于存放与Dojo widget相关的HTML和CSS文件。

    添加Web页面

    接下来我们需要为Web插件添加展示和控制Web页面的各个文件。整个页面由三个子页面构成:Catalog,Seller和Search。

    1.resources/ui/internal/page/PetStore.js

    PetStore.js是负责展示与控制整个页面的Dojo widget。它负责添加页面上方的navigation toolbar和页面的主体部分,如清单1所示:

    清单1.

dojo.provide("com.ibm.petstore.web.ui.internal.page.PetStore");

//Require the ajax widget needed to show the content
dojo.require("net.jazz.ajax.ui.PlatformUI");
dojo.require("net.jazz.ajax.ui.PageContainer");

//Require the repository widget needed to show the navigation toolbar
dojo.require("com.ibm.team.repository.web.ui.internal.Navbar");
dojo.require("com.ibm.team.repository.web.client.internal.RepositoryClient");
dojo.require("com.ibm.team.repository.web.ui.internal._JazzApplication");

(function(){
    dojo.declare("com.ibm.petstore.web.ui.internal.page.PetStore",
        com.ibm.team.repository.web.ui.internal._JazzApplication, {
   
        init: function(){
            this.initUI();
            //function in _JazzApplication, to start the whole page
            this.start();
        },
       
        initUI: function(){
            //create main content area
            var container = document.createElement("div");
            container.id = "outer-container";
            var pageArea = new net.jazz.ajax.ui.PageContainer();
            pageArea.domNode.insertBefore((
              new com.ibm.team.repository.web.ui.internal.Navbar()).domNode,  pageArea.domNode.firstChild);
            container.appendChild(pageArea.domNode);
   
            //add main content area to workbench
            var workbenchRootNode = net.jazz.ajax.ui.PlatformUI.getWorkbench().rootNode();
            workbenchRootNode.appendChild(container);
        }
     });
})();

    其中,dojo.provide和 dojo.require是Dojo定义的APIs,负责JavaScript的模块化管理。Jazz Ajax Framework的优化器通过这个模块化管理系统了解各个模块之间的依赖关系,从而达到动态最优化的目的。

    2.resources/ui/internal/page/templates/Catalog.html,resources/ui/internal/page/templates /Search.html,resources/ui/internal/page/templates /Seller.html

    这三个文件分别对应三个子页面Dojo widget的模板。我们拿Catalog.js为例说明,如清单2所示:

    清单2.

<div class="com-ibm-petstore-web-catalog">
    <table class="table">
        <tbody>
            <tr>
                <td width="25%">
                    <div dojoAttachPoint="_categories"/>
                </td>
                <td>
                    <div dojoAttachPoint="_detail"/>
                </td>
            </tr>
        </tbody>
    </table>
    <div class="copyright">
        Copyright(c) 2008 IBM
    </div>
</div>

    下面我们详细介绍其中的内容:

    最外层的Div定义了当前的widget。我们通过class属性为此div元素添加CSS信息,其目的有两个:首先它限定了当前widget风格的范围,从而不会与其他widget产生命名空间的冲突;其次当开发人员使用浏览器调试工具检查DOM节点时,有利于当前widget的识别。如清单3所示:

    清单3.

<div class="com-ibm-petstore-web-catalog">

    table定义了一个表单节点。class属性指定了table元素的风格,其定义在相应的CSS文件中。而dojoAttachPoint属性是Dojo的专用属性,能被Dojo toolkit识别,其目的是使该widget相应的JavaScript文件能直接访问该节点,如清单4所示:

    清单4.

<table class="table">
      …
    <div dojoAttachPoint="_categories"/>
      …
    <div dojoAttachPoint="_detail"/>
  …
</table>

    3.resources/ui/internal/page/Catalog.css,resources/ui/internal/page/Search.css,resources/ui/internal/page/Seller.css

    这三个文件分别对应三个子页面widget的风格。我们拿Catalog.css为例,上面的Catalog.html中有两个class属性,因此其CSS文件如清单5所示:

    清单5.

.com-ibm-petstore-web-catalog{ 
}

.com-ibm-petstore-web-catalog .table {
      width: 50%;
      height: 500px;
      text-align: center;
}

.com-ibm-petstore-web-catalog .copyright {
      text-align: center;
      color: blue;
}

    其中,.com-ibm-petstore-web-catalog定义了此文件的命名空间,而table和copyright则是具体风格的定义。

    4.resources/ui/internal/page/Catalog.js,resources/ui/internal/page/Search.js,resources/ui/internal/page/Seller.js

    这三个文件分别对应三个子页面的Dojo widget, 它们通过扩展点的方式自动加载Web应用。我们拿Catalog.js为例说明,如清单6所示:

    清单6.

dojo.provide("com.ibm.petstore.web.ui.internal.page.Catalog");

dojo.require("dijit._Widget");
dojo.require("dijit._Templated");
dojo.require("net.jazz.ajax.ui.PlatformUI");
dojo.require("com.ibm.team.repository.web.transport.ServiceResponseHandler");
dojo.require("com.ibm.petstore.web.client.PetStoreClient");
dojo.require("com.ibm.petstore.web.ui.internal.widget.CategoryItem");
dojo.require("com.ibm.petstore.web.ui.internal.widget.CategoryDetail");

(function(){

    //local variable, using in current widget namespace
    var ServiceResponseHandler = com.ibm.team.repository.web.transport.ServiceResponseHandler;
    var PetStoreClient = com.ibm.petstore.web.client.PetStoreClient;
   
    dojo.declare("com.ibm.petstore.web.ui.internal.page.Catalog", [dijit._Widget, dijit._Templated], {
   
        templatePath: dojo.moduleUrl("com.ibm.petstore.web", "ui/internal/page/templates/Catalog.html"),
       
        //inner variable
        detailPart: null,
        categories: [],
        items: [],
  
        // initialization
        postCreate: function(){                          
           …
        },
       
        catalogAction: function(){               
           …
        }
    });
})();

    其中dojo.provide声明了此widget的命名空间,它允许其他Dojo对象访问此widget。dojo.require用来通知Dojo去加载其他的类文件。例如当前widget需要加载一些Dojo widgets和用户自定义的widgets。

    清单7.

(function() {
...
})();

    清单7是Jazz Web UI中的编码惯例。它为当前widget创建单独的命名空间。

    清单8.

dojo.declare("com.ibm.petstore.web.ui.internal.page.Catalog", [dijit._Widget, dijit._Templated], {
      templatePath: dojo.moduleUrl("com.ibm.petstore.web", "ui/internal/page/templates/Catalog.html"),
});

    如清单8所示,dojo.declare声明了一个widget。第一个参数是widget的名字,其前缀必须与其在resources结构中的位置一致。第二个参数定义了widget的基类。第三个参数由一对{}包围,是此widget的具体实现,也称作widget的原型。

    清单9.

postCreate: function(){                          
      var ActionRegistry = net.jazz.ajax.ui.PlatformUI.getWorkbench().getActionRegistry();
      ActionRegistry.registerAction( "com.ibm.petstore.web.catalogAction", this, "catalogAction");
       this.detailPart = new com.ibm.petstore.web.ui.internal.widget.CategoryDetail();
}

    如清单9所示,postCreate是Dojo widget的初始方法。在此它注册成为一个action,对应下文中扩展点的声明,其具体实现方法是catalogAction。而new com.ibm.petstore.web.ui.internal.widget.CategoryDetail 创建了一个新的Dojo widget对象。

    清单10.

catalogAction: function(){     
      var responseHandler = new ServiceResponseHandler(this, "_success", "_error");         
      PetStoreClient.getAllCategoryDTOs(responseHandler, {}); 
},
_success: function(categories) {                 
      this.categories=categories;
      dojox.data.dom.removeChildren(this._categories);  
      …
      this._categories.appendChild(item.domNode);
},
_error: function(e) {
      …   
}

    如清单10所示,catalogAction是action的具体实现方法,它负责调用REST服务,并将结果反映在Web UI中。开发人员也可以使用Dojo 提供的统一事件处理方法使得用户与站点进行交互。

    5.将页面添加到Jazz Web UI扩展点中,如清单11所示:

    清单11.

<plugin>
    <extension point="net.jazz.ajax.webBundles">
        <prerequisites>
            <requiredWebBundle id="net.jazz.ajax"/>
            <requiredWebBundle id="com.ibm.team.repository.web"/>
            <requiredWebBundle id="com.ibm.team.process.web"/>
        </prerequisites>
    </extension>
   
    <extension id="PetStore" point="net.jazz.ajax.applications">
        <application alias="/web/PetStore"     jsclass="com.ibm.petstore.web.ui.internal.page.PetStore"/>
    </extension>
   
    <extension point="net.jazz.ajax.pages">
        <page id="com.ibm.petstore.web.Catalog"
         widget="com.ibm.petstore.web.ui.internal.page.Catalog"
         name="Catalog"
         defaultAction="com.ibm.petstore.web.catalogAction">
            <applicationScope id="com.ibm.petstore.web.PetStore"/>
            <action id="com.ibm.petstore.web.catalogAction"/>
        </page>
    </extension>
   
    <extension point="net.jazz.ajax.pages">
        <page id="com.ibm.petstore.web.Search"
         widget="com.ibm.petstore.web.ui.internal.page.Search"
         name="Search"
         defaultAction="com.ibm.petstore.web.searchAction">
            <applicationScope id="com.ibm.petstore.web.PetStore"/>
            <action id="com.ibm.petstore.web.searchAction"/>
        </page>
    </extension>
   
    <extension point="net.jazz.ajax.pages">
        <page id="com.ibm.petstore.web.Seller"
         widget="com.ibm.petstore.web.ui.internal.page.Seller"
         name="Seller"
         defaultAction="com.ibm.petstore.web.sellerAction">
            <applicationScope id="com.ibm.petstore.web.PetStore"/>
            <action id="com.ibm.petstore.web.sellerAction"/>
        </page>
    </extension>
   
</plugin>

    现在我们更新plugin.xml,将上述页面添加到Jazz Web UI中。此插件定义了五个扩展:

    · net.jazz.ajax.webBundles通知Equinox将当前插件中的资源加载到web命名空间中,并依赖于prerequisites中定义的Jazz Web基础构造。需要注意的是,如果当前插件是一个Web插件,要在此声明net.jazz.ajax包作为依赖项

    · net.jazz.ajax.applications定义当前Web应用的URI上下文信息application alias以及此 应用的实现类。其实现类是上文介绍的com.ibm.petstore.web.ui.internal.page.PetStore。

    · net.jazz.ajax.pages定义了具体的一个页面。当前插件中有三个页面:Catalog,Search和Seller。每个页面有唯一的id和显示在UI中的name,并声明其页面的具体实现widget。 applicationScope定义了当前页面的作用范围。action则与widget中具体的实现方法相对应,即3.4节中介绍的postCreate方法中,我们将com.ibm.petstore.web.catalogAction与catalogAction实现相关联。

    调用REST服务

    在Jazz架构模型中Jazz Web Client通过调用Rest Service和后端进行通信,其实质还是使用Ajax异步调用的方式,只不过Jazz做了一层封装,使得异步请求的代码书写更加简洁和通用。Jazz Web UI 通过TeamServerClient接口调用REST服务。例如上文Catalog.js的catalogAction方法调用PetStoreClient来处理REST请求,如果请求成功,则执行_success方法;否则执行 _error方法。PetStoreClient.js是TeamServerClient接口的具体实现如清单12所示:

    清单12.

dojo.provide("com.ibm.petstore.web.client.internal.PetStoreClient");

dojo.require("com.ibm.team.repository.web.transport.ServiceRequest");
dojo.require("com.ibm.team.repository.web.transport.TeamServerClient");

(function() {
    var ServiceRequest = com.ibm.team.repository.web.transport.ServiceRequest;
    var TeamServerClient = com.ibm.team.repository.web.transport.TeamServerClient;

    com.ibm.petstore.web.client.internal.PetStoreClient = {

        PETSTORE_SERVICE_URI: "com.ibm.petstore.common.service.rest.IPetStoreRestService",
   
    _invokeService: function(responseHandler, method, appArgs){
             var actualArgs= {};
             if (appArgs) {
                 var jsonArgs= dojo.json.serialize(appArgs);
                 actualArgs.jsonString= jsonArgs;
                 if(appArgs.category) {
                     actualArgs.category= appArgs.category;
                 }
             }
             var serviceRequest = new ServiceRequest(this.PETSTORE_SERVICE_URI, method, appArgs);
             TeamServerClient.invokeService(serviceRequest, responseHandler);
         },
   
         getAllCategoryDTOs: function(serviceResponseHandler, params){
             this._invokeService(serviceResponseHandler, "getAllCategoryDTOs", params);
         }
    };
})();

    PETSTORE_SERVICE_URI声明了将要调用的REST服务。请求参数是JavaScript对象。REST服务执行异步调用,将返回结果通过response handler传递给服务请求者。

    测试执行

    在Run->Run…中创建一个Jetty launcher,或者直接使用附录工程代码中的PetStore -Server Start.launch文件。在浏览器中输入http://localhost:9080/jazz/web/PetStore访问PetStore页面。其中"web/PetStore"对应于net.jazz.ajax.applications扩展点中所定义的application alias字段。执行结果为:


    图3. PetStore执行结果
 
总结

    本文以PetStore的用户界面开发为例,介绍了如何利用Jazz Ajax Framework开发胖客户端 Web UI的过程。通过对JAF的详细阐述和主要开发流程的介绍,可以帮助用户在Jazz平台上快速构建基于Web 2.0的企业级Web应用。

参考资料

    Writing a Jazz Web Hello World Page

    Using LeftNavigationLayout and TeamServerClient Interfaces

    Web UI Programming Model

0
相关文章