一些奇特的 JavaScript 功能
发布日期: 1/4/2005 | 更新日期: 1/4/2005
Jay Allen、Mark Davis、Heidi Housten 和 Dan Mohr
Microsoft Corporation
![*](http://img.microsoft.com/library/gallery/templates/MNP2.Common/images/3squares.gif)
在北半球这里,夏天已经过去了。孩子们回到了学校。秋天临近的迹象使小组的成员感染到了这种新入学的情绪。我们什么时候会长大呢?!我们采访了 Jay,还有他的女儿,因此他们要伴随 Web Team Talking 走进秋天。您可以期待更多的精彩内容。
除了扔湿纸团、理新发型之外,本月我们研究了 MSN® Messenger、一些奇特的 JavaScript 功能、如何更新服务器上的 XML 数据,以及多得让您看不完的短篇集。
如果您愿意学习,请将有关 Web 的问题发送给我们,我们会立即对您的问题作答。
本页内容
Instant Gratification
亲爱的 Web Team:
我可以将 MSN Instant Messenger 嵌入在诸如 msn.com 这样的网站上吗?
谢谢,
Joshua Carroll
Web Team 的答复:
我们越来越需要一种比 Instant Gratification 更好的功能,在这一方面,MSN Messenger 最有说服力。http://www.msn.com 中的 IM 功能是使用提供 Messenger 功能的两个 Microsoft ActiveX ® 控件以及提供 DHTML 用户界面的一些脚本来实现的。为简短起见,假定我要在 Intranet 主页中添加一个小的提要栏,以便在用户登录后列出该用户的联系人(及其连接状态),并在用户未登录时提供登录按钮。下面提供了一个示例,但是为了表明我们不是得到了神(请注意小写字母“o”)的暗示才领悟到这一信息,我们先概述一下所涉及的技术。
由于没有关于这些控件的文档,因此我们需要做一些研究工作来实现我们的目标。首先,我们可以查看 msn.com 使用的代码以获得一些提示。这不是因为缺乏勇气,而是通过艰苦地研究这些代码,我们可以从大体上了解这两个控件的功能,以及我们可以怎样编写自己的代码。我们可以使用的第二个工具(并且是有用得多的工具)是 OLE View。OLE View 是 Microsoft Visual Studio_ 附带的一个实用工具,它的功能很强大,可以分析并显示 COM 对象的类型信息。
因此,我们启动 OLE View,找到前面提到的两个控件(其友好名称分别为 MSN Messenger Application 和 MSN Messenger Object),右键单击这两个控件,然后选择 View type information。该视图将显示对象使用的各个接口和枚举。标记为调度接口的任何接口都应该可用于基于脚本的调用方,因此我们开始着手编码了!
在确保控件正确实例化以后,代码需要做的第一件事情是确定用户是否联机。这是通过检查 Messenger Object 的 LocalState 属性来完成的。该属性从 MSTATE 枚举(其定义已部分包含在我们的代码中)返回一个值。如果用户未联机,我们将启用调用 Messenger Application 的 LaunchLogonUI 方法的登录按钮。如果用户已联机,则我们需要使用用户的所有联系人来填充 DIV。为此,我们循环访问默认列表 (List(0)) 中的每个联系人,并检索其 FriendlyName 以及描述其当前状态的 MSTATE 值,然后新建一个包含此信息的 DIV。
由于我们要允许用户通过单击列表中的任何联系人来向该联系人发送消息,因此需要以编程方式将 onclick 事件处理程序附加到新 DIV。该事件处理程序函数 (sendMessage) 基本上是 Messenger Application 的 LaunchIMUI 方法的包装。由于 LaunchIMUI 将目标联系人作为一个参数,因此我们需要能够确定 Messenger Object 的默认列表中的哪个联系人与被单击的 DIV 对应。这时最理想的是使用 expando 属性。我们将联系人在默认列表中的索引附加到新建的、表示该联系人的 DIV 元素。这样,onclick 处理程序就能够检索到此索引,然后从 Messenger Object 的列表中抽取正确的联系人并将其传递给 LaunchIMUI。
本示例包含的一项额外功能是在该页加载后处理注销和登录。这是通过处理由 Messenger Object 激发的 OnLocalStateChangeResult 事件来完成的。此事件在 LocalState 属性更改时激发(并不太意外)。因此,相应地,我们仅仅查看我们已脱机还是已联机。如果已脱机,需要清空联系人 UI 列表;如果已联机,需要在短暂的 setTimeout(确保一切都正确同步)之后调用 populateContacts。而且由于 ActiveX 控件依赖于独立 Messenger 的运行实例,因此我们的页面还将更新以响应它触发的事件。
至此,您可能已对我们的喋喋不休感到厌烦,那么,请看下面的代码。
<HTML>
<HEAD>
<TITLE>Embedding MSN Messenger Test</TITLE>
<OBJECT CLASSID="clsid:F3A614DC-ABE0-11d2-A441-00C04F795683"
CODEBASE="#Version=2,0,0,83"
CODETYPE="application/x-oleobject" ID="oMsgrObj" WIDTH="0"
HEIGHT="0" OnUserStateChanged="alert();">
</OBJECT>
<OBJECT CLASSID="clsid:FB7199AB-79BF-11d2-8D94-0000F875C541"
CODETYPE="application/x-oleobject"
ID="oMsgrApp" WIDTH="0" HEIGHT="0">
</OBJECT>
<STYLE>
BODY
{
FONT-FAMILY: Verdana, Arial, Helvetica;
FONT-SIZE: 8pt;
}
INPUT
{
FONT-FAMILY: Verdana, Arial, Helvetica;
FONT-SIZE: 8pt;
}
.clsHeading
{
FONT-WEIGHT: bolder;
FONT-SIZE: 10pt;
}
.clsContact
{
PADDING: 2px;
CURSOR: hand;
}
</STYLE>
</HEAD>
<BODY onLoad="body_onLoad();">
<SCRIPT LANGUAGE="JScript">
// Here are the definitions for the Messenger enumerated values we use
var MSTATE_OFFLINE = 1;
var MSTATE_ONLINE = 2;
var MSTATE_BUSY = 10;
var MSTATE_BE_RIGHT_BACK = 14;
var MSTATE_IDLE = 18;
var MSTATE_AWAY = 34;
function body_onLoad()
{
// First, we need to make sure that the Messenger controls got instantiated correctly
if ("undefined" != typeof(oMsgrObj) && null != oMsgrObj.object && "undefined"
!= typeof(oMsgrApp) && null != oMsgrApp.object)
{
// If so, let's check to see if we're online and (if so) populate our contacts UI
if (oMsgrObj.LocalState == MSTATE_ONLINE)
{
populateContacts();
}
else if (oMsgrObj.LocalState == MSTATE_OFFLINE)
{
btnLogon.disabled = false;
}
}
else
{
// Uh, oh - the controls didn't get instantiated correctly;
// the user probably needs to install Messenger
alert("You need to install the latest version of MSN Messenger!
\nGo to http://messenger.msn.com right now!");
}
}
function populateContacts()
{
var oList = oMsgrObj.List(0);
var oContact;
var i;
// To populate our contact list, we're going to iterate throught the
// default list collection, check the state of each contact,
// and add a DIV with the approppriate appearance to our UI
for (i = 0; i < oList.Count; i++)
{
oContact = oList.Item(i);
oNewElement = document.createElement("DIV");
oNewElement.innerText = oContact.FriendlyName;
switch (oContact.State)
{
case MSTATE_ONLINE:
// Don't need to do anything
break;
case MSTATE_OFFLINE:
oNewElement.innerText += " (Offline)";
oNewElement.style.color = "graytext";
break;
case MSTATE_BUSY:
oNewElement.innerText += " (Busy)";
break;
case MSTATE_BE_RIGHT_BACK:
oNewElement.innerText += " (Be Right Back)";
break;
case MSTATE_IDLE:
oNewElement.innerText += " (Idle)";
break;
case MSTATE_AWAY:
oNewElement.innerText += " (Away)";
break;
default:
oNewElement.innerText += "(Just plain not around!)";
oNewElement.style.color = "graytext";
break;
}
oNewElement.className = "clsContact";
// To enable us to respond to the onclick event,
// we're programmatically setting the event
// handler AND we're defining an expando property
// on the contact DIV whose value is set to
// the index of the contact in the default list (so we can find them later)
oNewElement.onclick = sendMessage;
oNewElement.setAttribute("CONTACTID", i.toString());
divContacts.appendChild(oNewElement);
}
}
function doLogon()
{
// To logon, we just ask Messenger to display its logon UI
if (oMsgrObj.LocalState == MSTATE_OFFLINE)
{
btnLogon.disabled = true;
oMsgrApp.LaunchLogonUI();
}
}
function sendMessage()
{
// To send a message, we likewise just ask Messenger to do the heavy
// lifting (we just have to
// pass it a contact from the default list)
var nContactID = parseInt(window.event.srcElement.getAttribute("CONTACTID"));
if (!isNaN(nContactID))
{
var oContact = oMsgrObj.List(0).Item(nContactID);
oMsgrApp.LaunchIMUI(oContact);
}
}
</SCRIPT>
<SCRIPT LANGUAGE="JScript" EVENT="onLocalStateChangeResult(hr)" FOR="oMsgrObj">
if (hr == 0)
{
if (oMsgrObj.LocalState == MSTATE_ONLINE)
{
// Now we're online
window.setTimeout("populateContacts();", 3000);
}
else if (oMsgrObj.LocalState == MSTATE_OFFLINE)
{
// Now we're offline
divContacts.innerHTML = "";
btnLogon.disabled = false;
}
}
</SCRIPT>
<DIV CLASS="clsHeading">Contacts (click on a contact to send a message!)</DIV>
<DIV ID="divContacts" STYLE="MARGIN-TOP: 8px;
MARGIN-BOTTOM: 8px; BORDER: 1px solid steelblue"></DIV>
<INPUT TYPE="BUTTON" ID="btnLogon" VALUE="Logon" onClick="doLogon();" DISABLED="yes">
</BODY>
</HTML>
将 XML 发送回服务器
亲爱的 Web Team:
你们怎样修改客户端 Web 浏览器上的 XML 并将最后的 XML 发送到 Web 服务器呢?
Web Team 的答复:
我们假定您的 Web 服务器运行的是 Microsoft Internet Information Server (IIS) 4.0(或更高版本)或支持 Active Server Page (ASP) 技术的产品。
我们来简要地回顾一下 Web 浏览器是如何提供丰富的交互式 Internet 体验的。考虑最简单的形式:Web 浏览器从 Web 服务器请求一个页面,处理返回的信息(通常是超文本标记语言 (HTML) 格式),并显示结果。超文本传输协议 (HTTP) 定义了 Web 服务器和浏览器用来相互通讯的语言。Web 浏览器还执行大量的幕后工作,以便用户能够导航 Web 并显示各种媒体类型。
因此,您需要一个交互式 Web 站点。那么,用户怎样将信息发送到 Web 服务器呢?
Web 浏览器或 Web 应用程序有许多种用来将 XML 发送到 Web 服务器的方法,我们将介绍其中的两种。Web 浏览器通常使用 HTTP GET 方法来请求 Web 页,并使用 HTTP POST 方法来发送信息。要实现用户与 Web 服务器之间的通讯,最简单的方法是使用 HTML FORM 元素。
FORM 元素提供了一种使用 GET 或 POST 方法将数据发送到服务器的途径。将哪些控件置于窗体内由您决定,并且您还决定只有具有 NAME 属性的那些控件才能够提交其数据。例如,当用户单击 Submit 按钮时,每个控件的数据都发送到 Web 服务器。
在下面的示例中,存储在 XML 数据岛中的 XML 将作为窗体的一部分提交到服务器。可以通过多种方法获取此 XML:硬编码、通过设置 SRC 属性从外部文件中加载、从 ADO 记录集中检索、从 SQL Server 2000 中检索或者使用 XML DOM 脚本对象模型来创建/修改。可以通过将 XML 绑定到 HTML 元素来将 XML 呈现给用户(请参阅 Binding HTML Elements to Data)。将 XML 数据包含在表单中提交就像在 onsubmit 事件处理程序中将 XML 分配给隐藏的 INPUT 元素一样简单,如下所示:
<FORM ACTION="http://yourserver/form.asp" METHOD="post" NAME="myform"
ONSUBMIT=" myform.mytext.value = myxml.XMLDocument.xml;">
<INPUT TYPE="HIDDEN" NAME="Text" ID="mytext">
<INPUT TYPE="Submit">
</FORM>
<XML ID="myxml">
<root>
<item id="1" name="Item 1"/>
<item id="2" name="Item 2"/>
</root>
</XML>
下面是 FORM.ASP 页的内容:
<%@ language=JScript %>
<%
var xmldom = Server.CreateObject( "Microsoft.XMLDOM" );
var xml = Request.Form( "Text" );
Response.Write( "<XMP>" + xml + "</XMP>" );
%>
注为方便起见,本例中服务器返回 XMP 元素内的 XML。尽管此 HTML 元素是否决的,但是它具有将 HTML 和 XML 元素作为文本显示的好处。
XMLHttpRequest 对象是 Microsoft Internet Explorer 5.0 或更高版本附带的 MSXML 组件的一部分。该对象是在 Web 页上创建的,用于将 XML 发送到 Web 服务器。该对象是通过将 Microsoft.XMLHTTP 指定为应用程序名称来创建的。您通常调用 Open 方法、设置头信息(可选)、调用 Send,然后选中 responseXML 或 responseText 属性。使用此对象的一个好处是无需刷新页面即可执行 POST,从而实现了服务器与浏览器之间的无缝数据传输。Internet Explorer Web 服务行为使用 XMLHttpRequest 对象来提供对 .NET XML Web 服务的调用(使用 SOAP 请求)。
下面的示例展示了如何将 XML 文档发送到 Web 服务器。请注意,该示例使用同步调用。要进行异步调用,应该为 Open 方法的第三个参数提供 true 值,并使用 onreadystatechange 事件处理程序,以便在 Send 方法完成时得到通知。
<HEAD>
<script language="JScript">
function PostXml(xmldoc)
{
var xmlhttp = new ActiveXObject( "Microsoft.XMLHTTP" );
xmlhttp.Open( "POST", "http://yourserver/xmlhttp.asp", false );
xmlhttp.Send( xmldoc );
return xmlhttp.responseXML;
}
function ShowResults()
{
var resp = PostXml( myxml.XMLDocument );
mydiv.innerHTML = "<XMP>" + resp.xml + "</XMP>";
}
</script></HEAD>
<BODY>
<BUTTON ONCLICK="ShowResults()">Post XML</BUTTON>
<DIV ID="mydiv"></DIV>
<XML ID="myxml">
<root>
<item id="1" name="Item 1"/>
<item id="2" name="Item 2"/>
</root>
</XML>
下面是 XMLHTTP.ASP 文件的内容:
<%@ language=JScript %>
<%
// Load XML
var doc = Server.CreateObject( "Microsoft.XMLDOM" );
doc.load( Request );
// Process the XML ...
// Return result
Response.ContentType = "text/xml";
doc.save( Response );
%>
在许多情况下,将 XML 存储在客户端上能够为 Web 站点提供创新的功能,例如,缓存常用数据、用户个性化或内联数据编辑。我们已讨论了两种将 XML 数据发送到 Web 服务器的机制。使用 ASP 技术来处理服务器上的 XML 数据很简单,而使用 ASP.NET 中的新功能进行处理则更简单。服务器可以选择以多种方式处理 XML,例如,将信息保存到文件或数据库中,或者根据 XML 内容执行操作。
下面介绍了另外几种将数据发送到 Web 服务器的方法,这些方法超出了本文的讨论范畴。
• | HTTP GET 方法 — 使用所请求的 URL 的查询字符串(“?”后面的文本)来发送信息,但限定 URL 的大小不得超过 2K。 |
• | HTTP PUT 方法 — 采用此方法存在安全隐患。 |
• | HTML INPUT TYPE=FILE 元素可以用于上载用户选定的文件。 |
• | 直接从 ActiveX 控件或应用程序调用低级别的 WinInet 函数,以便可以更好地控制与 Web 服务器的通讯。 |
• | 使用 Internet Explorer Web 服务行为来调用对 .NET XML Web 服务的调用。 |
对日期进行排序
亲爱的 Web Team:
我尝试着显示特定文件夹的内容,但显示的文件是按照创建日期来排序的(而不是像我现在所希望的那样按字母顺序来排序)。是否存在一种方法,可以通过使用 ASP 和 FileSystemObject 来实现我的愿望呢?对于你们提供的任何帮助,我将不胜感激。
Matt Pierce
Web Team 的答复:
Matt,非常感谢您的提问!下面是一个示例 ASP 页(感谢 Eric 的工作!),该页使用 FileSystemObjectscripting 对象来获取文件名集合,然后按创建日期对其进行排序。有关 FileSystemObject scripting 对象的文档,可以在 Microsoft Windows Script Technologies 站点找到。
调用 GetFolder() 方法以检索文件夹对象的指定的路径。该文件夹对象包含可以使用 Enumerator 对象访问的 files 集合。每个文件项都是按照文件名的顺序检索的,因此将它们分配给一个数组以便在稍后对其进行排序。
arr[i++] = {path : fc.item().Path, date : fc.item().DateCreated};
上面显示的是一种不很常见的分配语法,这种语法称为对象文字,它包含用逗号分隔的属性规范列表。每个属性规范都由属性名及其后面的属性值组成。此对象文字语法用于创建具有 path 和 date 这两个属性的对象。
使用 Array 对象的 sort() 方法可以指定我们自己的比较函数 sortByDate,以便确定排序顺序。在本例中,comparison 函数返回一个指示两个项在创建日期上的相对顺序的值,但可以轻松地针对其他文件属性对该函数进行改写。
<%@ language=JScript %>
<%
function sortByDate(f1, f2)
{
if (f1.date < f2.date)
return -1;
else if (f1.date > f2.date)
return 1;
else
return 0;
}
function showfiles(path)
{
var fso, f, f1, fc, arr, i, s;
fso = new ActiveXObject( "Scripting.FileSystemObject" );
f = fso.GetFolder( path );
// Build the file list
arr = new Array();
i = 0;
for ( fc = new Enumerator( f.files ); !fc.atEnd(); fc.moveNext() )
arr[i++] = {path : fc.item().Path, date : fc.item().DateCreated};
// Sort the file list
arr.sort( sortByDate );
// Display the file list
s = "";
for ( i=0 ; i<arr.length ; ++i )
s += arr[i].path + "<br>";
return s;
}
Response.Write( showfiles( Server.MapPath( "." ) ) );
%>
Web Team 简介
Web 辅助功能
问:Rob 想知道怎样使 Web 站点易于访问。
答:请查看 Microsoft Enable 站点上的辅助功能 Web 指导原则。
针对色盲用户的开发注意事项
问:Becca 问:是否有关于色盲以及考虑到这一情况时的 Web 开发的任何信息。
答:请看 Robert Hess 在其定期专栏 More or Hess 中撰写的文章 Can Color-Blind Users See Your Site?。
行为
问:Pierre 问:DHTML 行为有什么用途。
答:MSDN 联机库中有一篇很好的概述文章介绍这些行为。DHTML Dude 专栏也在名为 Fun with Tables 的专栏中讨论了这一主题。
Intranet 身份验证
问:Paul 想知道公司用户怎样能够使用其域用户名和密码来自动通过 Web 站点的身份验证。
答:在 Internet Explorer 中,转至“工具”/“Internet 选项”,单击 Security 选项卡。然后单击 Custom Level,向下滚动至 User Authentication,并选择 Automatic logon only in Intranet zone。除非您公司的 IT 组配置了其他选项,否则此选项是默认选项。
Web Team
Mark Davis 是 Internet Explorer SDK 组的软件设计工程师。Mark 来自英格兰,他目前正在接受训练,为攀登西北部的主峰作准备。
Heidi Housten 是瑞典的一名 Microsoft 咨询服务顾问,在此之前,她在 Developer Support 组和 MSDN 组工作过一段时间。有人谣传说她搬至那里是为了躲避西雅图没完没了的小雨,实际上她去那里仅仅是为了参加八月的传统的小龙虾宴会。
Dan Mohr 是 Microsoft Developer Support 的 Internet Client 组的工程师,他业余时间喜欢在自己的地下室录制磁带、编写他的 Commodore 64 程序,以及吹捧 70 年代末期的朋克摇滚乐。
Jay Allen 是 Microsoft Developer Support 的 Internet Client 组的支持工程师,他渴望将记事本与 Emacs Lisp 集成。在工作之余,他将绝大部分时间都用来陪自己的四个孩子,剩下的一点时间则通常用来读数学书、学习日语以及用 Haskell 编程。
Web Team 大热门
Web Team 话题列表
转到原英文页面