文/ Jeremiah Talkar
信任
1. 聊天演示程序内在的想法,连同这篇文章一起都是我自己的。我所见过的所有聊天程序不是完全基于JAVA就是HTML。我的方法是这两种方法的一个很好的平衡。
2. 我通过在Netscape开发者站点阅读大量的文章后收集了在JAVA和JAVASCRIPT间通信的技术信息。
3. 我第一次在读Danny Goodman的文章时(The JavaScript Apostle on the Netscape site)偶然遇到术语“不知名的applet(Faceless applet)”。因此出于对Danny的信任我使用了这个非常切题的术语。
放弃
1. 这篇文章中讨论的技术已经在Windows 2000 Professional机器上使用Internet Explorer 5.0和 Netscape Navigator 4.7测试过了。在我所知道的最佳作品中,这些技术应该在任一浏览器4.0以上的版本上运行,但我不能保证我这样做,因为我没有时间用这些版本将它测试出来。
2. 因为JAVASCRIPT是唯一一种在两种主要浏览器都得到支持的语言,所有的脚本代码使用这种语言。我先前在http://www.ncompasslabs.com上使用了一个可用的商业插件,这个插件可以让Netscape Navigator支持VBScript,但我去他们的站点核实这个信息的时候,这个产品已经没有列出了。
引言
我一直从事现代COM的工作两年了,现在是我公司的e化商业产品团队的技术领导。我明白好的界面设计和在不同环境像Visual C++, Visual Basic 还有脚本下都能很好执行程序的重要性。基于界面的程序设计的能力在我的头脑中已经永远确立,并且作为一个软件工程师我试着将它应用到我所涉及的每件事情中。
在过去的一年里,当ASP+脚本就要被当作这些对象的黏合剂使用的时候,经验告诉我们的勤奋:所有的商业逻辑应该被压缩在COM对象之内。设计和开发典型的基础设施和商业对象要求一个更高的技术设备,它用来比较什么是要求实际使用同一个事物的。开发这些对象的首选环境(至少在我的团队中)是Visual C++ / ATL / STL。微软也鼓励Visual Basic成为这些对象可选择的开发环境。
这些对象被代表性的称为“不知名的”,因为他们实现许多逻辑但用户界面除外。它的表现层次(presentation tier)不是一个充足的客户就是一个不充足的客户(这个客户有从最终用户表达信息和聚集信息的逻辑)。然后这样的一个客户用这些信息通过使用不知名的对象做一些有意义的事情。这整个想法是当商业对象改变不频繁时表现层次会代表性的要求许多客户定制。表现层次要求的改变也可以通过使用较少的经验程序来实现。
应用这些相同的原理到浏览器环境,当使用一种脚本语言处理表现方面时,在“不知名的”二进制模块中压缩高度发展的客户方的逻辑看起来是合乎逻辑的。在Windows平台上这些模块的选项是Java applet和ActiveX 控件/服务器。这篇文章的焦点是使用Java applet完成这个目标,因为applet是独立于浏览器,平台和处理器的(对大部分而言)。
Applet的简短历史
SUN Microsystems1995年在嘹亮的号角声中引入了Java applet。Applet立即博得WEB世界的欢心,因为它们增加了在原来是一个静态HTML的世界的浏览器中动态地显示WEB内容的能力。
在最初的日子里,使用Java applet作为最好的一种在网页中增加动态内容的方式出现了。最初Microsoft 试图使用他们的ActiveX Control技术反对SUN提供的Java applet,但是在网页内部使用控件存在两个主要问题:
● 二进制模块是处理器指定的,因此不适合作为网页的一部分运行。万维网(World Wide Web)如此成功的一个主要原因是使用W3C标准HTML写出的大部分网页对浏览器和处理器是不可知的事实。ActiveX控件正好不符合这个范例。
● 安全是一个大问题,因为控件编写者有足够的权利在客户机器上存取资源。签了名的控件允许任何一个人查看网页并聪明地作出是否应该在他/她的机器上下载一个特定控件的决定,但是所有他进行的是一个按钮意外的点击(或者是高兴地忽略),这样就留下了易受攻击的客户机,这也正符合恶意控件编写者的意图。
当动态HTML终于开始成型时,事情彻底地改变了。文档对象模型(Document Object Model )作为可以设计的组件,它们用它们自己的属性和方法揭露了网页元素。即使Internet Explorer和Netscape Navigator浏览器执行动态HTML有许多不同,但使用脚本代码程序化的改变显示页内容本身的根本主题就是是一个巨大的成功。Applet突然开始看起来是又旧又粗糙的。W3C对动态HTML的认可最终对高度发展的,动态网页的新种类调整了语气。
在浏览器内是使用Java applet有以下列出的几种优点:
● Applet(对大多数Applet而言)可以在多浏览器,平台和处理器上工作。
● JAVA语言是典型的一种强大的概念性语言。
● JDK有许多典型的且只在高层类库中创建的有用的类。
● 技术中已经构思了安全,applet只能用默认值在方框中运行。如果它们要打破方框的限制则Applet必须是已经签了名的。
● Applet可以就发回用户化信息,上传/下载文件等而与网页服务器取得联系。签了名的applet可以与任何一个服务器联系,而不仅仅是一个它们的主机。
● 通过“查看源文件”选项不能看到applet代码,因此保护了知识产权。
● JAVA的.class文件非常小,结果是下载非常快。
使用Java apple的缺点:
● 在一次浏览器对话中,下载applet不是网页使用它们的第一时间显示就是网页随后刷新后显示。Applet在浏览器对话中不长驻客户机。事实上在大多数场合下这可以作为一种优势考虑。
● Applet要花很长时间初始化。
● 因为JAVA的.class文件是被JAVA虚拟机(Java virtual machine ,JVM)解释的字节代码,所以applet运行比本地代码慢。
● 一个applet只是浏览器上真正状态的一部分,它不会无缝完好地出现在网页内容中。层叠样式单(Cascading style sheets ,CSS)也不会直接影响applet占有的矩形区域。
● Netscape Navigator 4.x有十个活动applet的限制。我不知道Internet Explorer 4.0+有任何一种这样的限制。
使用Java applets的快速回顾
Java applets通过使用用applet标签被包含进一个HTML网页中。W3C 站点上HTML 4.01说明书的13.4节详细地说明了这个标记。它也提到赞成<object>,而不赞成使用这个标记。
一个简单地包含applet的HTML网页如下所示:
<html>
<head>
<title>Calculator</title>
</head>
<body>
<applet id="Calculator" width="300" height="500" code ="Calculator.class" codebase=".">
<param name="InitialMode" value="Normal">
</applet>
</body>
</html>
上例中用到的属性解释如下:
Id
Applet实例的标识符。客户方脚本代码能知道使用这个id的applet。
Width
这个属性指定了applet显示域的初始宽度(不包括applet创建的窗口或者对话框)。即使我经成功使用了宽度0用 “放弃”一节中提到的浏览器,还是推荐使用值1作为最小的可能宽度。
Height
这个属性指定了applet显示域的初始高度(不包括applet创建的窗口或者对话框)。就像用宽度属性一样,推荐使用值1作为最小的可能宽度。
Code
这个属性指定了任一类文件(包含applet编译的applet子类或者能够得到类的路径,包括类文件自身)的名字,它在谈到applet的codebase时会作出解释。
Codebase
这个属性指定applet的基本URI. 如果这个属性没有指定,那么它默认最近的文档为同一个基本URI。
只有code, width和height 属性是必须的。
Param标记包含一对名字的值,它允许applet第一次运行时安装自己。
在上面的applet调用一个方法的JAVASCRIPT函数如下,它非常简单:
<script language=Javascript>
function SetCalculatorMode(Mode)
{
document.Calculator.SetCalculatorMode (Mode);
// Alternative way to reference the applet.
// document.applets[0].SetCalculatorMode(Mode);
}
</script>
劳动力的分工
在这篇文章的引言部分,我暗示当通过JavaScript代码处理表现形式时,高度发展的浏览器方正处理的一个方法是被压缩进不知名的Java applets中的。这个方法要求在Java和JavaScript间双向通信。下面几章会研究可用的选项。
通过JAVASCRIPT代码存取Java applet暴露的成员和函数是直截了当的,像上一节中的SetCalculatorMode()函数说明的一样。文档内的applet不是通过使用它的Id / Name就是使用applet收集的索引来查询。
例如:
document.Calculator.SetCalculatorMode(Mode);// or document.applets[0].SetCalculatorMode(Mode);
使用netscape.javascript.JSObject类和netscape.javascript.JSException类完成了其它方向(Java to JavaScript)的通信。为了找到这些类在什么位置,我在我的硬盘驱动器上寻找所有包含字符串“JSObject”的文件。令我吃惊的是,这些文件在许多不同的程序中得到广泛地使用,包括是Visual Interdev工程一部分的库。
如果Netscape Navigator 4.0+安装在你的机器上,这些.class文件在 <Navigator Installation Directory>\communicator\program\java\classes 目录下的Java40.jar 文件中可用。
我也在<Windows Installation Directory>\Java\Packages目录下的四个不同的.zip文件中寻找这些.class文件。这些.zip文件显然是Microsoft产品安装的,因为它们包含许多com.ms包。它们的用途是这两个类对任一浏览器都是可用的,你可以设置你的CLASSPATH环境变量给任一个上述路径。一个可选的方法是使用一个程序像WINZIP从.jar或者.zip文件中解压缩这些文件到你的applet目录下。
JSObject类
引用JSObject类成员函数的简短描述是为了更好的理解这个类的用途。
public static JSObject getWindow (Applet applet )
这个静态方法对含有给出的applet窗口返回一个JSObject。例如:JSObject MainWindow = JSObject.getWindow ( this );
public Object call ( String methodName, Object args[ ] )
这个函数从Java applet内部调用一个JavaScript方法。例如:
JSObject MainWindow = JSObject.getWindow ( this );
String Arguments[ ] = {"90", "2"}; // {"Percent complete", "Time remaining"}
MainWindow.call ( "UpdateProgressIndicator", Arguments );
public Object eval ( String s )
这个方法求一个JavaScript表达式的值。表达式是这个对象上下文中待求的JavaScript源代码的一个字符串。例如:
JSObject MainWindow = JSObject.getWindow ( this );
JSObject UserName = MainWin.eval ( "document.UserInfoForm.UserName" );
public Object getMember ( String name )
这个方法检索JavaScript对象的一个索引成员,等价于JavaScript对象的this.name。:
JSObject MainWindow = JSObject.getWindow ( this );
JSObject DocumentPage = (JSObject)MainWindow.getMember ( "document" );
JSObject UserInfoForm = (JSObject) DocumentPage.getMember ( "UserInfoForm" );
JSObject UserName = (JSObject) UserInfoForm.getMember ( "UserName" );
public Object getSlot ( int index)
这个方法检索JavaScript对象的一个索引成员,等价于JavaScript对象的this [index]。例如:
JSObject MainWindow = JSObject.getWindow ( this );
JSObject DocumentPage = (JSObject)MainWindow.getMember ( "document" );
JSObject Applets = (JSObject) DocumentPage.getMember ( "applets" );
Object theApplet = Applets.getSlot ( index );
public void removeMember ( String name )
这个方法删除一个JAVASCRIPT对象的指定成员。
public void setMember ( String name, Object value )
这个方法设置一个JAVASCRIPT对象的指定成员。它等价于JavaScript对象的this.name = value。例如:
JSObject MainWin = JSObject.getWindow ( this );
JSObject DocumentPage = (JSObject) MainWin.getMember ( "document" );
JSObject UserInfoForm = (JSObject) DocumentPage.getMember ( "UserInfoForm" );
JSObject UserName = (JSObject) UserInfoForm.getMember ( "UserName" );
UserName.setMember ( "value", "Jeremiah S. Talkar" );
public void setSlot ( int index, Object value )
这个方法设置一个JAVASCRIPT对象的索引成员。它等价于JavaScript对象的this[index] = value。
public String toString ()
这个方法将JSObject转换成一个字符串。
上面的例子是很清楚的。JSObject类的公共方法试图在JavaApplet中调用JavaScript函数时是不受限制的。他们也可以使一个applet直接处理文档对象模型元素。
这些类完整的文档可以在http://developer.netscape.com/docs/manuals/communicator/jsref/pkg.htm上得到。
文档也解释了怎样在Java和JavaScript间处理数据类型。
MAYSCRIPT属性的意义
即使applet使用JSObject调用JavaScript函数,或者直接访问文档对象模型,如果applet标签没有包含在MAYSCRIPT属性中的话JSObject的方法就会失败。这一点能使网页设计者测定一个applet是否能唤起JavaScript。
不知名的applet间的通信
在网页内使用不知名的,但可以再度使用的Java applet时,一个applet需要与另一个applet直接通信是可能的。这样的一个呼叫也可以通过一个媒介JavaScript函数通信,但熟悉所有可用的选项总是比较好的。
java.applet包的AppletContext接口对applet的上下文实行一个访问限制,像插入applet的浏览器,一个applet在网页上还有其它的applet也在同样一个网页上。
例如,下面是一个HTML页包含两个applet:
html>
<head>
<title>Communication between applets</title>
</head>
<body>
<applet code="CircleArea.class" name="CircleArea" width=1 height=1>
</applet>
<applet code="PICalculator.class" name="PICalculator" width=1 height=1>
</applet>
...
</body>
</html>
下面的代码表明了AppletContext对象的用法:
AppletContext context = getAppletContext();
PICalculator PIApplet = (PICalculator) context.getApplet ( "PICalculator" );
PIApplet.getValueOfPI();
另一个选项是使用AppletContext::getApplets()方法,它返回了一个访问文档内所有applet的枚举类型。即使applet间的通信是标准Java applet API的一部分,它也不会在所有允许JAVA的浏览器中得到支持。其它applet的核心编码也是坚定不移的。最好的方法可以是使用JAVASCRIPT函数来处理这种通信。
JAVA的IUnknown::QueryInterface类对象是类层次的根。每个类有一个超类对象。所有对象,包括数组,实现这个类的方法。Object::getClass()方法返回有许多能探索JAVA类自身详细资料的有用的函数的“类”。虽然在这篇文章的前面部分已经作过详细的解释,我还是想指出getInterfaces()方法使动态接口的发现通过一个JAVA类实现。
我没有试过在JAVASCRIPT内调用getClass()方法,因此不能评论它的可行性。但是使用基础类型的applet,这种功能性可以轻易地使它对脚本代码可用。
安装示例文件
这篇文章的示例代码已经在Java2JavaScript.zip文件中压缩。示例文件演示了一个已经在客户方自身简单地发送消息的一个聊天程序。用户在不同的机器上加入这个聊天中,一条消息发送给服务器,然后广播给所有的参加者。
组成示例的文件是:
ISession.java
定义ISession 接口的源文件
ISession.class
Isession接口的Java字节代码
ChatClient.java
演示ChatClient applet的源文件
ChatClient.class
ChatClient applet类的Java字节代码
CompileChatClient.bat
简单的JAVA源文件的批处理文件
TestChatClient.htm
支持ChatClient applet HTML文件
JSObject.class
JSObject类的字节代码
JSException.class
JSException类的字节代码
安装和运行示例程序的步骤是:
● 在运行有Personal Web Server或者Internet Information Server的机器上解压缩Java2JavaScript.zip到你所选择的目录。
● 确保安装目录下的netscape\javascript 子目录中的JSObject和JSException类文件是可用的。
● 右击安装目录选择“属性”。
● 点击“网页共享”标签,选定“共享这个文件夹”单选按钮,在弹出的对话框中接受默认值“虚拟目录”。
● 最后,启动Internet Explorer 4.0+ 或者 Netscape Navigator 4.0+程序,然后输入URL http:// <机器名>/<虚拟目录>/TestChatClient.htm。
在输入框中输入一些文本,然后点击相应的“SEND”按钮,你就会看到消息在聊天窗口中出现。
示例代码解释
聊天程序是网页上流行的合作机制。我见过的聊天程序有两种类型:
● 一种是管理用户界面和服务器通信的Java applet。
● 另一种是每隔几秒就通过自动刷新来显示最后一次刷新后的所有新消息的HTML网页。
最近我不得不为我们E化商业产品的聊天程序实现产品质量。经过许多考虑后,我决定使用一个混合的方法。首先最重要的,Java applet是执行Isession接口的一个不知名的applet。
public interface ISession
{
// Type is used to differentiate the actual message string
// and can be set to ‘Text’, Hyperlink’ etc.
// Should be invoked first to indicate to the server that a
// new person has joined the chat.
public int BeginSession(String strAuthor, String strOptions, String strType, String strMessage);
// Should be invoked when the author wants to exit the chat.
public int EndSession(String strAuthor, String strType, String strMessage);
// Used to send the chat messages.
public int SendMessage(String strAuthor, String strType, String strMessage);
}
我已经把聊天程序产品版本中的这个接口作了轻微地修改,包括EndSession()和SendMessage()的Author参数。这是因为我的演示程序使用一个applet在两个不同聊天者间传递聊天消息。
ChatClient.java文件是Isession接口的实际执行文件。如果它们执行时浏览器就调用函数init(), start()和stop()。描述文档窗口的JSObject是在执行init()方法后得到的。
// Get the JavaScript window that will have the various scripts that this applet will call.
m_JScriptWin = JSObject.getWindow(this);
因为Java applet调用两个不同的JavaScript函数,我决定使WEB开发者能像applet的参数一样指定这些函数的名字,直到为同一个applet提供默认值。
m_strMessageHandler = getParameter("MessageHandler");
m_strErrorHandler = getParameter("ErrorHandler");
BeginSession()和EndSession()是只执行SendMessage()的虚拟执行函数。
SendMessage()呼叫在m_strMessageHandler成员变量中指定的JavaScript函数名字。默认值是“HandleSessionMessage”。相关代码如下所示:
if (m_JScriptWin != null)
{
String Arguments[] = {strAuthor, strType, strMessage};
m_JScriptWin.call(m_strMessageHandler, Arguments);
}
HandleSessionError()调用一个在m_strErrorHandler成员变量中指定的JavaScript函数名字。默认值是“HandleSessionError”.
TestChatClient.htm文件处理聊天的表现方面。applet使用<applet>标记包含在网页中。
<applet id="ChatApplet" width="1" height="1" code="ChatClient.class" codebase="." VIEWASTEXT mayscript>
<param name="MessageHandler" value="HandleSessionMessageEx">
<param name="ErrorHandler" value="HandleSessionErrorEx">
</applet>
参数指定applet调用的两个JavaScript函数的名字。我只表明名字除默认值外说明了这个方法是多么灵活,网页中的两个窗体模拟两个人正与对方聊天。相关的HTML也是简单易懂的。
实际上消息本身是在<DIV>上显示的。在Internet Explorer中,我使用表格对象模型在单个的行中显示每条消息。因此ChatMessagesTable是在<DIV>上定义的。
最后,HandleSessionMessagesEx()JavaScript函数处理所有的表现方面。在Internet Explorer内,每条消息发出后ChatMessagesTable表就增加新行。如果需要也将显示滚动条。在Netscape Navigator内,我给Messages变量附加新消息,并使用后者更新ChatMessages <DIV>。既然滚动条不会自动在Netscape <DIV>(是一个真正的LAYER)上显示,那么我在顶部显示最后一条接收到的消息。我已经找到了在Navigator内怎样支持LAYER滚动条的文章,但是与这个范例无关。
最后的思考
这篇文章努力介绍浏览器方逻辑的一些(优雅的)技术。像我早先在这篇文章中提到的,JSObject在许多程序中使用广泛,包括微软公司。同样,你需要考虑你的个人环境来确定这里介绍的技术是否可用。
至于这篇文章所附的示例程序,我觉得允许JavaScript / DHTML实现聊天的介绍使这个代码能够通过入门水平/低级程序员维护。用户界面的定制使用DHTML / JavaScript也很容易。加之,它允许看上去与余下的网页内容相一致,且更强大的表现技术。
在这个程序的产品版本中,我增加了在参与者机器上交换打开的超连接,使用层叠样式单动态的选择消息颜色等的支持。
欢迎任何信息反馈!