跳到导航
BEA Dev2Dev Oracle and BEA
首页 资源中心 dev2dev学堂 在线技术论坛 User Group CodeShare
dev2dev 首页 > 资源中心 > 技术文章
在BEA WebLogic Portal 8.1中进行Ajax编程,第2部分

时间:2006-06-01
作者:John Margaglione
浏览次数:
本文关键字:WebLogic PortalportalAjaxproxy servletESB门户企业服务总线
文章工具
推荐给朋友 推荐给朋友
打印文章 打印文章

  编辑注:本文讲述了在WebLogic Portal 8.1中进行底层Ajax编程的细节。这与当前的WebLogic Portal 9.2 beta版无关。

摘要

  Ajax是一种异步编程范型,借助于它,开发人员可以创建高度交互式的Web站点,从而在提高用户效率的同时减轻服务器的负载。Ajax结合了Web services、JavaScript和动态HTML编程技术,可以创造丰富的客户端体验,并提高本地应用程序的可用性。本系列文章的第一篇介绍了这些相关概念以及在BEA WebLogic Portal中实现一个Ajax解决方案所涉及的架构考虑事项。

  本文是此系列文章的最后一篇,提供了一组复杂的示例portlet,从一些基本的查找portlet开始,再到一个使用动态表作为前端数据库表的高级例子,最后以一个有趣的例子结尾,最后这个例子在没有使用<object>或<iframe>标签的情况下把一些页面动态嵌入到另一个页面中。

  所有例子均给出了完整的源代码,并包括安装脚本和文档。

关于示例Portlet

  本文在这里给出了各种支持Ajax的portlet,并逐行对其源代码(在本文的下载部分中可以找到)进行了解释。本文还包括了一个可重用Java servlet的源代码,这个servlet可用于降低Mozilla Firefox和Microsoft Internet Explorer (IE)中固有的安全限制级别。这个servlet实现了第1部分中描述的代理设计模式。

  有一点需要指出,我在代码中使用了最简单的Ajax库,因为我想说明整个过程到底是怎么回事。还有大量可用的库可以极大地减少需要编写的代码量。我在一些代码中使用了Sarissa库,主要用于处理一些跨浏览器的问题。参见Ajax简介,可以了解有关Ajax库的更多信息,比如DWR。当设计和构建自己的生产应用程序时,我不建议您使用这里给出的底层构件。本文中使用的Ajax库可以很好地处理跨浏览器的问题和Ajax绑定(wiring)。

  本文的余下部分逐个描述了每个示例portlet。我们鼓励您浏览源代码,从而更深入地了解这些portlet是如何实现的。

Zip Code Lookup

  Zip Code Lookup(区号查找)portlet说明了两个可用于调用远程Web服务(例如,一个与Web服务器位于不同计算机上的Web服务)的方法。这两个方法(direct和proxy)是通过使用两个直接命名的按钮Direct和Proxy来调用的。Direct方法直接从Web浏览器调用Web服务,这通常会导致违反Internet Explorer中的安全规则,而在Firefox中则根本无法使用。Proxy方法使用了本文稍后将会描述的Java代理servlet。

Zip Code Lookup

  图 1:调用远程Web服务的Zip Code Lookup portlet

  注意Zip Code标签旁边的输入栏。我们在这一栏中获得输入值,然后把它传递给后端的Web服务。当我们从该Web服务获得城市和州的信息时,就会把这些信息放在City和State标签右边的相应位置上。

  为了能够引用JavaScript代码中的元素,我们需要为每个元素指定一个惟一的ID。下面给出了zipcode.jsp的相关HTML代码:

<body style="font-family:helvetica;font-size:10pt;">
  <p>Type in a 5-digit zip code to get information on that zip code.</p>
  <form name="zipcode" method="post" action="">
    <table cellspacing="0" cellpadding="4" frame="box" bordercolor="#dcdcdc" rules="none" style="border-collapse: collapse;">
      <tr>
        <td>Zip Code</td>
        <td><input type="text" size="5" name="zipcode_USZip" id="zipcode_USZip"></td>
        <td><input type="button" onclick="zipcode_updateDirect();" value="Direct"/></td>
        <td><input type="button" onclick="zipcode_updateProxy();" value="Proxy"/></td>
      </tr>
    </table>
  </form>
  <hr color="blue"/>
  <table>
    <tr>
      <td>City</td>
      <td><div id="zipcode_city" style="color:blue;"></div></td>
    </tr>
    <tr>
      <td>State</td>
      <td><div id="zipcode_state" style="color:blue;"></div></td>
    </tr>
    <tr>
      <td colspan="2">
        <form>
            <button onclick="zipcode_showResults();">Returned XML <span id="zipcode_status" style="color:blue;"></span>
         </button>
     </form>
   </td>
 </tr>
  </table>
</body>

  注意,所有ID标签都使用了portlet名称作为前缀。这是一个相当好的最佳实践,您必须将其深植到您的编码风格中。为什么这很重要呢?因为当BEA WebLogic Portal呈现一个门户页面时,它会把所有的portet HTML拷贝和粘贴到一个聚合页面上。所以,如果您有两个portlet,而每个portlet都有一个ID为“mylabel”的元素,那么呈现出来的门户页面中将会有两个元素具有相同的惟一ID。当使用JavaScript/DOM函数getElementById('mylabel')时,返回的元素将会是文档中“mylabel” ID的第一个实例,而这很可能不是您所期望的结果!

  还要注意使用<div>标签作为动态内容的占位符。这是一个样式选择问题,但是我发现,<div>标签具有成为任何内容的容器的巨大潜力,而<td>标签的限制就要严格得多。更多地使用CSS绝对有助于减少使用嵌入的样式标签,但这将会是留给读者的一道练习题。但是,我要指出的是,明智地使用CSS样式可以极大地减少最终要编写的JavaScript数量。记住,CSS可以用于改变一个元素的颜色、位置、可见性、透明度以及其他许多可视化特性。简单的样式交换通常可以让图形化用户界面变得更加具有交互性。在Employee Listing portlet中,当变化被提交给数据库之后,我使用了这项技术来通知用户。

  在这个portlet中,我们将从文本栏中获得输入,调用一个Web服务查找与区号相关的城市和州信息,然后使用该Web服务返回的信息动态更新City 和State <div>元素。页面不会刷新——只有City和State元素会更新。

调用Web服务

  将被这个portlet调用的Web服务有一个端点在http://www.webservicex.net/uszip.asmx/GetInfoByZIP上,并带有一个参数USZip。所以,获取区号60118的相关城市和州信息的调用应该是http://www.webservicex.net/uszip.asmx/GetInfoByZIP?USZip=60118。这是使用Ajax调用Web服务的惟一方法。您可以创建一条相应的SOAP消息,但是这已经超出了本文的讨论范围。

  如果我们通过把上面的查询键入到浏览器中,来使用USZip=60118手动调用Web服务,则该Web服务将使用下面的XML文档做出响应。

<?xml version="1.0" encoding="utf-8" ?> 
<NewDataSet>
    <Table>
       <CITY>Dundee</CITY> 
       <STATE>IL</STATE> 
       <ZIP>60118</ZIP> 
       <AREA_CODE>847</AREA_CODE> 
       <TIME_ZONE>C</TIME_ZONE> 
    </Table>
</NewDataSet>

  我们想提取出CITY和STATE元素,并把它们放到我们的Web页面中相应的占位符中。

  我们将使用Direct按钮的onclick处理程序来执行一小段嵌入的JavaScript zipcode_updateDirect()。下面给出这些JavaScript代码:

function zipcode_updateDirect() {
  var url = "http://www.webservicex.net/uszip.asmx/GetInfoByZIP?USZip=";
  var zipValue = document.getElementById("zipcode_USZip").value;
  
  // Open a URL connection using the XMLHttpObject.  The third parameter specifies that the 
  // call should be made asynchronously.  Set this to false to make this call synchronous.
  zipcode_http.open("GET", url + escape(zipValue), false);
  
  // Set a callback handler to a local JavaScript method
  zipcode_http.onreadystatechange = zipcode_handleHttpResponse;
  
  // Make the call.  You can replace the null value with XML request data if you are doing
  // a SOAP-style call instead of using HTTP request parameters.
  zipcode_http.send(null);
}

  下面列出了使用XmlHttpObject调用远程Web服务的(几乎是)最简单的过程:

  • 调用XmlHttpObject.open()。
  • 为XmlHttpObject设置一个回调处理程序。
  • 调用XmlHttpObject.send()。

  在这种情况下,Web服务是通过使用常规的服务端点直接调用的。当然,这会导致Firefox中出现一些安全问题。在Internet Explorer中,用户将会看到一个对话框,询问他们是否允许这项操作。

Proxy servlet

  那么我们应该如何避免这个问题呢?我们使用代理设计模式来创建一个中间服务,该服务驻留在Web服务器上,并负责调用远程服务,然后以XML的形式返回结果(或文本形式,内容是什么无关紧要)。对于浏览器来说,服务似乎与Web站点位于同一台主机上,所以不会出现安全问题。有多种实现这个代理的方式,包括实现为Web服务或Java servlet。我使用了一个servlet,因为它具有快速和易用的特点。该servlet应该尽可能简单,而且调用起来要和原来的Web服务一样简单。所以,我们让这个servlet在查询字符串中使用一个任意的参数列表,其中有一个特定参数叫做“url”,它代表着服务端点的URL。来自Web服务的结果应该被逐字返回给调用方。

  参照第一篇文章中的架构图(图1和图2),您可以更加直观地了解到direct和proxy调用之间的差别。

  下面给出了ProxyServlet的相关代码,我把这些代码放到了我的Portal项目中的WEB-INF/src/demo目录下:

package demo; 

import java.io.*;
import java.net.*;
import java.util.Enumeration;
import javax.servlet.ServletException;
import javax.servlet.http.*;

public class ProxyServlet extends javax.servlet.http.HttpServlet { 
    protected void service(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {
        String surl = URLDecoder.decode(request.getParameter("url"));
        
        Enumeration enum = request.getParameterNames();
        boolean first = true;

        if(request.getParameterMap().size() > 1) {
             surl += "?";
        }
        
        while(enum.hasMoreElements()) {
            String name = (String) enum.nextElement();
            
            if(!name.equals("url")) {
                if(!first) surl += "&";
                surl += name + "=" + request.getParameter(name);
                first = false;
            }
        }
        
        System.out.println("Redirecting to " + surl);

        URL url = new URL(surl);
        
        InputStream is = url.openConnection().getInputStream();
        
        while(true) {
            byte[] bytes = new byte[128];
            int read = is.read(bytes);
            if(read <= 0) break; 
            response.getOutputStream().write(bytes, 0, read);
        }
    }
    
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        service(request, response);
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        service(request, response);
    }

    protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        service(request, response);
    }
}

  对于这个要公开的servlet来说,需要修改WEB-INF中的web.xml文件。下面给出了添加的相关代码:

<servlet>
  <servlet-name>ProxyServlet</servlet-name>
  <servlet-class>demo.ProxyServlet</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>ProxyServlet</servlet-name>
  <url-pattern>/ProxyServlet</url-pattern>
</servlet-mapping>

  代理servlet带有常规的Web服务URL,并使用它作为url参数的值。所以,我们对区号查找Web服务的调用将转换为对下面这个代理servlet的调用:/Portal/ProxyServlet?url= http://www.webservicex.net/uszip.asmx/GetInfoByZIP&USZip=60118

  从JavaScript代码的角度来看,对代理servlet的调用有点像直接调用Web服务,但是还是有一些小的修改:

function zipcode_updateProxy() {
  // This is the endpoint for the zip code Web service
  var endurl   = escape("http://www.webservicex.net/uszip.asmx/GetInfoByZIP");

  // This is the endpoint of the proxy servlet that will make the call on our behalf
  var localurl = "/Portal/ProxyServlet";
  
  // This is the input parameter for the zip code
  var zipValue = document.getElementById("zipcode_USZip").value;
  
  // This is the actual URL that will be called using the XmlHttpRequest object
  var url = localurl + "?url=" + endurl + "&USZip=" + zipValue; // The server-side script

  // Open a url connection using the XMLHttpObject.  The third parameter specifies that the 
  // call should be made asynchronously.  Set this to false to make this call synchronous.
  zipcode_http.open("GET", url, true);
  
  // Set a callback handler to a local JavaScript method
  zipcode_http.onreadystatechange = zipcode_handleHttpResponse;
  
  // Make the call.  You can replace the null value with XML request data if you are doing
  // a SOAP-style call instead of using HTTP request parameters.
  zipcode_http.send(null);
}

处理异步返回

  Web服务调用的异步处理与Ajax技术的正确使用是密不可分的。这让事情变得稍微有一点复杂,但并没有您所想的那么复杂。异步调用的一个额外好处就是,您可以以可视化的方式向用户显示调用的状态(在本文稍后的Page Loader例子中,这显得更加重要)。一些Ajax帮助器库可以让使用响应处理程序的过程变得更加简单,但是这样做会让您失去处理不同就绪状态的能力,而您可以使用这些就绪状态向用户提供反馈。

  下面给出了用于处理一个Ajax回调的模板代码:

function zipcode_handleHttpResponse() {
  if (zipcode_http.readyState == 4) {
    if(zipcode_http.status == 200) {
    // Do something with the results
    }
    else {
      // Show the user a status message
    }
  }
  else {
      // Show the user a status message
  }
}

  这个模板相当简单,但是功能很强大。第一个分支检查了查询的readyState。想要了解有关就绪状态和响应状态的更多信息,请参见David Teare的Ajax简介

更新DOM元素

  更新一个DOM元素(比如<div>标签)的过程通常是很直观的。我说“通常”,是因为在大多数用例中,这个过程是简单的,而且可以在浏览器之间进行移植。可以使用下面的代码从返回的XML中提取CITY和STATE元素,并把它们放到Web页面上的<div>元素中。

// Turned the returned XML indo a DOM document that
//  we can manipulate
var doc = (new DOMParser()).parseFromString(
                        zipcode_http.responseText, "text/xml");

document.getElementById('zipcode_status').innerHTML  = 
                                        zipcode_http.statusText;

var cityObj  = doc.getElementsByTagName('CITY')
                                    .item(0).firstChild.data;
var stateObj = doc.getElementsByTagName('STATE')
                                    .item(0).firstChild.data;

// Set the form fields with the new data
document.getElementById('zipcode_city').innerHTML=zipcode_city;
document.getElementById('zipcode_state').innerHTML=zipcode_state;

  是不是很简单呢?下面这个portlet与这个portlet有很多相同之处,为了简短起见,我们将忽略这些相同的元素。

Stock Quote Lookup Portlet

  这个示例portlet(Stock Quote Lookup,股票报价查找)十分类似于Zip Code Lookup portlet,但是返回的XML使用了命名空间,这需要Internet Explorer进行不同的处理。请参见图2中的用户界面。

Stock Quote Lookup Portlet

  图 2:使用命名空间调用远程Web服务的Stock Quote Lookup portlet

  所调用的服务是http://ws.cdyne.com/delayedstockquote/delayedstockquote.asmx /GetQuote?LicenseKey=0&StockSymbol=beas。下面是从该服务返回的XML:

<?xml version="1.0" encoding="utf-8" ?> 
<QuoteData xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns="http://ws.cdyne.com/">
  <StockSymbol>BEAS</StockSymbol> 
  <LastTradeAmount>9.01</LastTradeAmount> 
  <LastTradeDateTime>2005-08-26T14:00:00.0000000-04:00</LastTradeDateTime> 
  <StockChange>-0.03</StockChange> 
  <OpenAmount>9.02</OpenAmount> 
  <DayHigh>9.05</DayHigh> 
  <DayLow>8.97</DayLow> 
  <StockVolume>1719106</StockVolume> 
  <MktCap>3.541B</MktCap> 
  <PrevCls>9.04</PrevCls> 
  <ChangePercent>-0.33%</ChangePercent> 
  <FiftyTwoWeekRange>6.42 - 9.86</FiftyTwoWeekRange> 
  <EarnPerShare>0.358</EarnPerShare> 
  <PE>25.25</PE> 
  <CompanyName>BEA SYSTEMS INC</CompanyName> 
  <QuoteError>false</QuoteError> 
</QuoteData>

  把这个结果与Zip Code例子返回的XML进行比较。命名空间在上面是以高亮显示的。Internet Explorer中对于命名空间的处理与Firefox中不同,必须对处理过程进行一些修改。下面给出了用于处理每个版本的代码:

if(isMSIE) {
    // I have no idea why this is necessary for this particular XML result set,
    // but it is.  If you can come up with a cross-browser way to make this one work,
    // please send me an email at jmargagl@bea.com!
    var nodes = doc.documentElement.childNodes;
    for(var i = 0; i < nodes.length; i++) {
        if(nodes[i].baseName == 'CompanyName') name = nodes[i].text;
        if(nodes[i].baseName == 'LastTradeAmount') last = nodes[i].text;
        if(nodes[i].baseName == 'StockChange') change = nodes[i].text;
    }
}
else {
    name = doc.getElementsByTagName('CompanyName')[0].firstChild.data;
    last = doc.getElementsByTagName('LastTradeAmount')[0].firstChild.data;
    change = doc.getElementsByTagName('StockChange')[0].firstChild.data;
}

  注意,Firefox在找到标签的方式方面没有任何特别之处,因为默认的命名空间已经在顶级元素处的XML中指定了。下一个例子还涉及到了特殊的命名空间,即命名空间中的每个元素的作用域都限定在该命名空间中。处理过程是类似的,但是这两种情况比较而言,Firefox中的实现要简单和清晰得多。

Employee Management Portlet

  现在要给出的这个portlet(Employee Management,员工管理)与上面的完全不同。Employee Management portlet(参见图3)使用Ajax来调用位于WebLogic实例上的一个Web服务。然后,这个Web服务从本地的Pointbase数据库中查询一个表中的记录,并把这些记录返回为格式化的XML文档,而且这些XML文档的架构已经创建好了。它还具有使用相同的Web服务来维护这些记录的能力。

Employee Management Portlet
图 3:使用一个 Web服务调用数据库后端的Employee Management portlet

  首先来看一看我们要使用的数据库表。下面这段脚本用于创建必需的表和插入一些测试行(位于create.sql文件中):

CREATE TABLE "WEBLOGIC"."DEMO_EMPLOYEE" (
    ID INTEGER  NOT NULL,
    FirstName VARCHAR (32)  NOT NULL, 
    LastName VARCHAR (50)  NOT NULL,  
    Email CHARACTER (100),
    CONSTRAINT demo_employee_pk PRIMARY KEY ( ID) 
);

INSERT INTO WEBLOGIC.DEMO_EMPLOYEE VALUES (1, 'John', 'Doe', 'jdoe@nowhere.com');
INSERT INTO WEBLOGIC.DEMO_EMPLOYEE VALUES (2, 'Jane', 'Doe', 'jdoe2@nowhere.com');
INSERT INTO WEBLOGIC.DEMO_EMPLOYEE VALUES (3, 'Bill', 'Huevo', 'bhuevo@nowhere.com');
INSERT INTO WEBLOGIC.DEMO_EMPLOYEE VALUES (4, 'Larry', 'Ellison', 'lellison@nowhere.com');
INSERT INTO WEBLOGIC.DEMO_EMPLOYEE VALUES (5, 'Bob', 'Loblaw', 'bloblaw@nowhere.com');
INSERT INTO WEBLOGIC.DEMO_EMPLOYEE VALUES (6, 'Sarah', 'Johnson', 'sjohnson@nowhere.com');
INSERT INTO WEBLOGIC.DEMO_EMPLOYEE VALUES (7, 'Kelly', 'Cobain', 'kcobain@nowhere.com');
INSERT INTO WEBLOGIC.DEMO_EMPLOYEE VALUES (8, 'Rusty', 'Ziebart', 'rziebart@nowhere.com');

  在这个例子中,Web服务由处理数据库更新的数据库控件提供支持。下面给出了这个Web服务的代码:

package WebServices.EmployeeListing; 

import demo.EmployeesDocument;
import java.util.Collection;
import java.util.Iterator;

/**
 * @common:xmlns namespace="demo" prefix="ns0"
 */
public class EmployeeListing implements com.bea.jws.WebService
{ 
    /**
     * @common:control
     */
    private WebServices.EmployeeListing.EmployeeDBControl employeeControl;

    static final long serialVersionUID = 1L;

    public static class Employee {
        String firstName;
        String lastName; 
        
        public Employee(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName  = lastName;
        }
    }
    
    /**
     * @common:operation
     * @jws:return-xml schema-element="ns0:Employees"
     */
    public demo.EmployeesDocument getEmployees()
    {
        WebServices.EmployeeListing.Employee[] employees = employeeControl.getEmployees();
        
        demo.EmployeesDocument doc = demo.EmployeesDocument.Factory.newInstance();
        EmployeesDocument.Employees emps = doc.addNewEmployees();
        EmployeesDocument.Employees.Employee emp;
        
        for(int i = 0; i < employees.length; i++) {
            emp = emps.addNewEmployee();
            emp.setId(employees[i].getID());
            emp.setFirstName(employees[i].getFirstName().trim());
            emp.setLastName(employees[i].getLastName().trim());
            emp.setEmail(employees[i].getEmail().trim()); 
        }
        
        return doc;
    }

    /**
     * @common:operation
     */
    public void deleteEmployee(int ID)
    {
        employeeControl.removeEmployee(ID);
    }

    /**
     * @common:operation
     */
    public int addEmployee(String firstName, String lastName, String email)
    {
        int ID = (int) System.currentTimeMillis();
        employeeControl.addEmployee(ID, firstName, lastName, email);
        
        return ID;
    }

    /**
     * @common:operation
     */
    public int updateEmployee(int ID, String firstName, String lastName, String email)
    {
        employeeControl.updateEmployee(ID, firstName, lastName, email);
        return ID;
    }
}

  要使用getEmployees方法调用Web服务,需要使用这个URL:http://localhost:7001/Portal/WebServices/EmployeeListing /EmployeeListing.jws/getEmployees,所用的XML模式是:

<xs:schema targetNamespace="demo" 
    xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    elementFormDefault="qualified" 
    attributeFormDefault="unqualified">
  <xs:element name="Employees">
    <xs:annotation>
      <xs:documentation>Employee Listing</xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:sequence>
        <xs:element name="Employee" minOccurs="0" maxOccurs="unbounded">
          <xs:complexType>
            <xs:all>
              <xs:element name="id" type="xs:int"/>
              <xs:element name="firstName" type="xs:string"/>
              <xs:element name="lastName" type="xs:string"/>
              <xs:element name="email" type="xs:string"/>
            </xs:all>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

  调用这个Web服务所返回的XML如下:

<?xml version="1.0" encoding="utf-8" ?> 
<demo:Employees xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:demo="demo">
  <demo:Employee>
    <demo:id>1</demo:id> 
    <demo:firstName>John</demo:firstName> 
    <demo:lastName>Doe</demo:lastName> 
    <demo:email>jdoe@nowhere.com</demo:email> 
  </demo:Employee>
  <demo:Employee>
    <demo:id>2</demo:id> 
    <demo:firstName>Jane</demo:firstName> 
    <demo:lastName>Doe</demo:lastName> 
    <demo:email>jdoe2@nowhere.com</demo:email> 
  </demo:Employee>
</demo:Employees>

  如果您试着在JavaScript/DOM代码中使用标准的getElementsByTagName()方法,将获得奇怪的结果。这是由于Internet Explorer和Firefox实现命名空间解析的方式不同。Firefox实现了一次单独的调用来解析命名空间,而Internet Explorer也使用同样的调用,但是带有的参数不同。下面给出了处理这个结果集的正确方式:

var html = '<table class="flashy"><tr>';
html += '<th class="flashy">ID</th>';
html += '<th class="flashy">First Name</th>';
html += '<th class="flashy">Last Name</th>';
html += '<th class="flashy">Email</th>';
html += '</tr>';

var elem;

if(isMSIE) 
    elem = doc.getElementsByTagName("demo:Employee");
else 
    elem = doc.getElementsByTagNameNS("demo", "Employee");

var i = 0;
for(i = 0; i < elem.length; i++) {
    html += '<tr>';
    
    var ID, firstName, lastName, email;
    
    if(isMSIE) {
        ID = elem[i].getElementsByTagName("demo:id")[0].firstChild.data;
        firstName = elem[i].getElementsByTagName("demo:firstName")[0].firstChild.data;
        lastName = elem[i].getElementsByTagName("demo:lastName")[0].firstChild.data;
        email = elem[i].getElementsByTagName("demo:email")[0].firstChild.data;
    }
    else {
        ID = elem[i].getElementsByTagNameNS("demo","id")[0].firstChild.data;
        firstName = elem[i].getElementsByTagNameNS("demo","firstName")[0].firstChild.data;
        lastName = elem[i].getElementsByTagNameNS("demo","lastName")[0].firstChild.data;
        email = elem[i].getElementsByTagNameNS("demo","email")[0].firstChild.data;
    }
        
    html += '<td class="flashy">' + ID + '</td>';
    html += '<td class="flashy">' + firstName + '</td>';
    html += '<td class="flashy">' + lastName + '</td>';
    html += '<td class="flashy">' + email + '</td>';
    html += '</tr>'
}

html += '</table>';

document.getElementById('employee_table').innerHTML = html;

现在动态修改内容怎么样呢?我们需要添加用于添加、修改和删除记录的Ajax方法,使之成为一个真正有用的portlet。顺便说一句,这是一个重用性很强的模板。以这种方式,您可以对多种类型的可重复性数据项进行建模,从而减少用户敲击键盘和页面刷新的次数。

  您可能注意到了,在本节开始部分的屏幕快照上,有一行是以黄色显示的。这是因为,我为此portlet添加了一项新功能,使用黄色显示数据尚未提交给数据库的行。实际上,一行显示为黄色的时间还不到一秒钟,但是如果它长时间保持黄色,用户就会知道系统速度很慢或者干脆已经崩溃,那么这时或许有必要通知IT部门。

  为了实现这项功能,我使用了JavaScript技术来修改一个元素的CSS样式,从而改变其外观。一开始,我为TD元素定义了两种CSS样式,flashy和flashy-new。第一种样式适用于表中的现有单元格。下面给出了样式定义:

td.flashy {
    background-color: #DDDDDD;
    foreground: blue;    
    margin: 3mm;
    padding-left: 2mm;
    padding-right: 2mm;
    border: none;
}

  现在,我们需要使用另一种样式来高亮显示一行:

td.flashy-new {
    background-color: yellow;
    foreground: blue;    
    margin: 3mm;
    padding-left: 2mm;
    padding-right: 2mm;
    border: none;
}

  当加入新的一行时,我使用了下面的代码来更新表和发送信息给Web服务:

   

function employee_onAdd() {
  window.status = "onAdd() called";
  
  // Get the values to add
  var firstName = document.getElementById("employee_FN:new").value;
  var lastName = document.getElementById("employee_LN:new").value;
  var email = document.getElementById("employee_E:new").value;
  
  // Call the Web service addEmployee method
  var url = '/Portal/WebServices/EmployeeListing/EmployeeListing.jws/addEmployee?';
  url += "firstName=" + Sarissa.escape(firstName) + "&";
  url += "lastName=" + Sarissa.escape(lastName) + "&";
  url += "email=" + Sarissa.escape(email);
  
  var table, tbody, row;
  
  try {
      table = document.getElementById("employee_table");
      row = document.createElement("TR");
      row.setAttribute("id", "employee_TR:-1");
      var cell, field;
      
      field = createInput("text", firstName, "employee_FN:-1");
      cell = createCell(field);
      row.appendChild(cell);
        
      field = createInput("text", lastName, "employee_LN:-1");
      cell = createCell(field);
      row.appendChild(cell);
        
      field = createInput("text", email, "employee_E:-1");
      cell = createCell(field);
      row.appendChild(cell);
      
      // Create the buttons
      var modifyButton = createInput("button", "Modify", "employee_M:-1");
      var deleteButton = createInput("button", "Delete", "employee_D:-1");
      
      cell = document.createElement("TD");
      if(isMSIE) {
        cell.setAttribute("className", "flashy-new");
      }
      else {
        cell.setAttribute("class", "flashy-new");
      }
      
      cell.appendChild(modifyButton);
      cell.appendChild(document.createTextNode(" ")); // Need a little space between buttons.
      cell.appendChild(deleteButton);
      row.appendChild(cell);
            
      tbody = document.getElementById("employee_tbody");
      tbody.appendChild(row);
  }
  catch(ex) {
    window.alert("Error: " + ex);
    tbody.deleteChild(row);
  }
    
  try {
    window.status = "Sending AJAX request to " + url;
    employee_http.open("GET", url, true);
    employee_http.onreadystatechange = employee_handleAddReturn;
    employee_http.send(null);
  }
  catch(ex) {
    window.alert("Could not send request");  
  }    
    
  employee_clearEntryFields();
}

  注意,添加新行时,您将通过调用setAttribute来设置CSS样式。还要注意,在Internet Explorer和Firefox中,用于设置CSS类的调用是不同的。Internet Explorer调用的是CSS样式className,而Firefox调用的则是class。

  在这个例子中,还有用于在出现错误时删除新行的代码。在JavaScript中,您也可以使用try/catch处理程序,就像在Java中一样。记住,当使用JavaScript编码时,要使用最佳实践,就像使用Java一样。企业应用程序的良好程度取决于其最弱的一环——不要让您的Ajax代码中出现这样的一环。

  这个portlet比前面任何例子都要复杂得多。我强烈建议您仔细研究本文中所包含的源代码,以便更好地了解DOM/CSS编程是怎么一回事。

Page Loader Portlet

  现在,我们将要讨论的是一个容易而且有趣的portlet(Page Loader,页面加载器),您可以把它作为另一个Web页面的输入,并把它加载到<div>元素中。假如内容管理系统是插入到WebLogic Portal中的,那么这个portlet不是特别有用。

  注意,某些Web站点不能正确加载图片。目标Web页面无法使用指向资源的相关链接,使之正确工作。站点仍然会进行加载,但是图片将会被图片占位符和ALT文本所代替。通过采用从Web服务调用返回的结果并给所有相关链接添加基址引用,就可以解决这个问题。无疑,这是留给读者的又一道练习题。

  为了使页面变得更加美观,并说明如何使用从XmlHttpObject异步返回的状态码,我们在页面右上方加上了一个小圆点,用于说明页面加载时的状态:灰色代表没有连接,绿色代表已连接/正在处理,而红色代表出现了错误。当页面完全加载时,该圆点将变回灰色。请参见图4。

Page Loader Portlet
图 4: Page loader portlet使用Proxy servlet来加载任意URL

  我们将使用ProxyServlet来获取被请求的页面。这样使用Ajax有些奇怪,但是它说明了涉及到Web时,所有内容都是服务!毕竟,Web页面本质上是HTML,而HTML是XML的一个子集:

function pageloader_update() {
  // Change the stoplight so that the user knows what is going on
  document.images.stoplight.src = "/Portal/resources/images/greenlight.gif";
  
  // This is the input parameter for the page to load
  var page = document.getElementById("page_to_load").value;
  
  // This is the URL that the user entered
  var endurl   = escape(page);

  // This is the endpoint of the proxy servlet that will make the call on our behalf
  var localurl = "/Portal/ProxyServlet";
  
  // This is the actual URL that will be called using the XmlHttpRequest object
  var url = localurl + "?url=" + endurl; // The server-side script

  // Open a url connection using the XMLHttpObject.  The third parameter specifies that the 
  // call should be made asynchronously.  Set this to false to make this call synchronous.
  pageloader_http.open("GET", url, true);
  
  // Set a callback handler to a local JavaScript method
  pageloader_http.onreadystatechange = pageloader_handleHttpResponse;
  
  // Make the call.  You can replace the null value with XML request data if you are doing
  // a SOAP-style call instead of using HTTP request parameters.
  pageloader_http.send(null);
}

  到现在为止,这些内容应该都是您很熟悉的。用于处理返回的页面的代码很简单:

function pageloader_handleHttpResponse() {
  if (pageloader_http.readyState == 4) {
    if(pageloader_http.status == 200) {
        var doc = Sarissa.getDomDocument();
        doc = (new DOMParser()).parseFromString(pageloader_http.responseText, "text/xml");
        
        document.getElementById('pageloader_html').innerHTML = pageloader_http.responseText;
        
        // Change the stoplight so that the user knows what is going on
        document.images.stoplight.src = "/Portal/resources/images/graylight.gif";
    }
    else {
        document.getElementById('pageloader_html').innerHTML = pageloader_http.responseText;
        document.images.stoplight.src = "/Portal/resources/images/redlight.gif"; 
    }
  }
  else {
        // Change the stoplight so that the user knows what is going on
        document.images.stoplight.src = "/Portal/resources/images/greenlight.gif";    
        document.getElementById('pageloader_html').innerHTML = "Loading...";
  }
}

  只要把来自XmlHttpObject的responseText映射为想要用作浏览器部件的<div>元素的innerHTML即可。

  注意,默认情况下,portlet将会调整大小以适应返回的整个Web页面。通过使用某些样式,可以使<div>标签看起来更像是一个窗口:

<div id="pageloader_html" 
     style="border: thin gold dashed; 
        height: 450px; 
        overflow-x: hidden; 
        overflow-y: auto; 
        width: 98%; 
        padding: 5px">
   <p>This is where the Web page will be displayed</p>
</div>

下载

  ajax_demo.zip

结束语

  用户需要响应灵敏度更高、内容更加丰富的Web应用程序。人们可以接受的性能指标每天都在变化。Ajax编程可以帮助开发人员在程序与用户的交互方面实现更大的灵活性。通过使用标准技术和一些最佳实践,Ajax可以帮助您解决很多让您头痛的难题,让您可以开始创建高度交互式的、用户驱动的Web站点,从而在竞争中立于不败之地。

  随着Ajax获得的关注越来越多,我们正在期望出现大量优秀的开源和专用工具,让Ajax编程变得更加轻松。BEA正在把Ajax集成到它即将推出的门户产品中,而且很多公司已经发行了功能全面的Ajax开发环境,比如JackBe

参考资料

原文出处:http://dev2dev.bea.com/pub/a/2006/03/ajax-portal-2.html

 作者简介
John Margaglione 是BEA Systems公司的系统工程师,他的工作是为BEA的渠道合作伙伴提供支持。John还是Sun认证的J2EE企业架构师。
dot dot dot

dot
  作者其它文章
您对本文的评价
您对这篇文章的看法如何?
太棒了!5分 不错啊 4分 一般般 3分 有待提高 2分 不好 1分

   
相关技术