JavaScript And Ajax(JavaScript 基本示例)
这里通过一个示例,演示使用 js 为费时的页面改善用户体验。
创建 JavaScript 页面处理器
多少次单击网页后,只看到 IE 的那个小球不停的转动,好像永不停息?是你的因特网连接速度下降了吗?和后端系统的连接有错误了吗?系统比较慢?这些问题通常会使新的基于 Web 的解决方案变得复杂。
遇到这种情况,应向用户提供进度信息,让他们知道系统正在处理他们的请求,以便帮助用户重建对程序的信心,改善用户体验。
提供状态信息的一个常用方法是使用 js 创建一个标准的页面处理器。用户从一个页面导航到另一个需要长时间处理的页面时,页面处理器立刻出现并显示一个标准信息(可能是滚动的文本)。同时,被请求的页面在后台下载。一旦结果页面可用,页面处理器消息就被请求的页面取代。
把 js 代码加到目标页面不能解决处理延时的问题。因为在页面处理结束和呈现的 HTML 返回给用户前这些代码不会被处理!但是,你可以创建一个通用的页面处理器,由它处理网站中所有耗时的页面的请求。
创建这样一个页面处理器,需要响应 onload 和 onunload 事件。下面的 PagePrecessor.aspx 页面示范了这种模式,它显示了一个表格,添加了一些文本及事件:
<body onload="BeginPageLoad();" onunload="EndPageLoad();">
<form id="frmPageLoader" runat="server" method="post">
<div>
<table width="99%" border="0">
<tr>
<td align="center" valign="middle">
<span id="MessageText">Loading Page - Please Wait</span>
<span id="ProgressMeter"></span>
</td>
</tr>
</table>
</div>
</form>
</body>
需要请求该页面处理器,并把实际要访问的页面作为查询字符串参数传入,例如,如果要加载 TimeConsumingPage.aspx,应该使用这个 URL:
PagePrecessor.aspx?Page=TimeConsumingPage.aspx
这个页面处理器只需要很少的服务器端代码。其实,它所要做的只是从查询字符串中获得原始请求的页面并把它保存到一个受保护页面类变量里(这点非常有用!因为以后你就可以使用 ASP.NET 数据绑定表达式使该变量对 js 代码公开,稍后就会看到)。
这是 PagePrecessor.aspx 页面的服务器端代码:
public partial class PageProcessor : System.Web.UI.Page
{
protected string PageToLoad;
protected void Page_Load(object sender, EventArgs e)
{
this.PageToLoad = Request.QueryString["Page"];
}
}
剩余的工作由客户端的 js 完成。当页面处理器第一次加载时,onload 事件被触发,它调用客户端的 BeginPageLoad() 函数。这个函数将保持当前窗口打开并开始获取用户请求的页面。需要使用到 window.setInterval() 方法(它设置一个定时器定期调用自定义的 UpdateProgressMeter() 函数)。
这是 js 函数 BeginPageLoad() 的代码:
<script type="text/javascript">
var iLoopCounter = 1;
var iMaxLoop = 6;
var iIntervalId;
function BeginPageLoad() {
// Redirect the browser to another page while keeping focus
location.href = "<%=PageToLoad %>";
// Update progress meter every 1/2 second.
iIntervalId = window.setInterval
("iLoopCounter=UpdateProgressMeter(iLoopCounter,iMaxLoop)", 500);
}
</script>
第一行代码把页面指向新 URL 。需要注意到的是,你想要下载的页面并没有硬编码到 js 代码中。相反,它由数据绑定表达式动态设置,页面在服务器上呈现时,ASP.NET 自动插入 PageToLoad 变量的值。
UpdateProgressMeter() 方法定期修改状态信息,使之看起来更像是动态的进度表:
function UpdateProgressMeter(iCurrentLoopCounter, iMaximumLoops) {
var progressMeter = document.getElementById("ProgressMeter");
iCurrentLoopCounter += 1;
if (iCurrentLoopCounter <= iMaximumLoops) {
progressMeter.innerHTML += ".";
return iCurrentLoopCounter;
} else {
progressMeter.innerHTML = "";
return 1;
}
}
最后,当页面完全加载后,客户端 onunload 事件被触发,调用 EndPageLoad() 函数停止计时器,清空进度信息并设置新页面在浏览器呈现后立刻消失的临时变化信息:
function EndPageLoad() {
window.clearInterval(iIntervalId);
var progressMeter = document.getElementById("ProgressMeter");
progressMeter.innerHTML = "Page loaded - Now Transferring";
}
整个过程中没有回传。最终结果是一个进度信息,它在目标页面被完全处理和加载后消失!
为了测试这个页面处理器,可添加一个页面来进行测试:
protected void Page_Load(object sender, EventArgs e)
{
System.Threading.Thread.Sleep(5000);
}
现在可以尝试请求页面处理器并转向到这个页面:
只要一小段客户端 js 代码,就可以让用户知道页面正在处理。因为用户知情,他感觉性能和体验也上升了。
使用 JavaScript 异步下载图片
先前的示例演示了如何创建更具响应性的界面,这种便利性不仅仅在于页面处理器,还可以使用 js 在后台下载页面较耗时的部分,虽然需要做更多工作,但它可以提供更好的用户体验。
加入,要在 GridView 里显示记录列表,其中一个字段显示小图像。它需要一个专门的页面获取图像,根据你的设计,它可能要为每条记录分别访问文件系统或数据库。多数情况下,你可以优化这一设计(例如,在绑定网格前把图片预先加载到缓存),但是如果图片来自第三方就不可行了。
下面这个示例正是这种情况。它显示一些书并从 Amazon 网站获取相关的图片。
记录数很大的时候,显示完整列表需要花不少时间。可以使用立刻出现的占位图像解决这一问题,真正的图像在后台获取并在获得后立刻显示。这样的做法显示含有全部图片的列表的总时间不会变化,但用户能够在图片下载前就开始阅读和滚动数据,图片加载速度的缓慢易于被用户接受。
这个例子先创建显示 GridView 的页面,代码使用一个 XML 文件的静态列表填充 DataSet 并绑定网格:
protected void Page_Load(object sender, EventArgs e)
{
DataSet ds = new DataSet();
ds.ReadXml(Server.MapPath("Books.xml"));
GridView1.DataSource = ds.Tables["Book"];
GridView1.DataBind();
}
XML 文件的内容:
<?xml version="1.0" encoding="utf-8" ?>
<Books>
<Book Title="Expert C# Business Objects" isbn="1590593448" Publisher="Apress"></Book>
<Book Title="C# and the .NET Platform" isbn="1590590554" Publisher="Apress"></Book>
<Book Title="Beginning XSLT" isbn="1590592603" Publisher="Apress"></Book>
<Book Title="SQL Server Security Distilled" isbn="1590592190" Publisher="Apress"></Book>
</Books>
XML 数据并不包括任何图片信息。这些细节需要从 Amazon 网站获取。GridView 直接绑定到可用的列(Title、isbn、Publisher),然后通过另一个页面根据 ISBN 获取相应的图像。
这是不包含样式信息的 GridView 控件标签:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False">
<Columns>
<asp:BoundField DataField="Title" HeaderText="Title" />
<asp:BoundField DataField="isbn" HeaderText="ISBN" />
<asp:BoundField DataField="Publisher" HeaderText="Publisher" />
<asp:TemplateField>
<HeaderTemplate>
Book Cover
</HeaderTemplate>
<ItemTemplate>
<img src="UnknowBook.gif" onerror="this.src='UnknowBook.gif';"
onload="GetBookImage(this,'<%# DataBinder.Eval(Container.DataItem,"isbn") %>');" />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
创新的是最后一列,它包含一个 <img> 标签。这个标签没有直接指向 GetBookImage.aspx,src 特性被设置为一个本地图像文件,它可以被快速下载显示。然后,onload 事件在缺省图像第一次显示后立刻发生,开始在后台下载真正的图像并在完成后开始替换。为了确保在发生错误时,不至于显示一个红色 X ,代码处理了 onerror 事件。
onload 事件调用 js 函数,传入当前图像控件的引用以及图书的 ISBN,ISBN 通过数据绑定表达式得到。js 函数调用另一个页面为图书获得图片,它把 ISBN 作为查询字符串参数指定自己希望获得的图片:
function GetBookImage(img, url) {
// Detach the event handler(the code makes just
// one attempt to get the picture)
img.onload = null;
// Try to get the picture from the GetBookImage.aspx page.
img.src = 'GetBookImage.aspx?isbn=' + url;
}
GetBookImage.aspx 页面执行获取所需图像的耗时任务,这可能涉及访问 Web 服务或连接数据库。这里,GetBookImage.aspx 页面直接把处理工作交给一个叫做 FindBook 的专门类,一旦得到 URL,它重定向页面:
protected void Page_Load(object sender, EventArgs e)
{
FindBook findBook = new FindBook();
string imageUrl = findBook.GetImageUrl(Request.QueryString["isbn"]);
Response.Redirect(imageUrl);
}
FindBook 类复杂一些。它抓取屏幕查找 <img> 标签以获得 Amazon 网站上的图片。遗憾的是,Amazon 的图像缩略图没有明确的命名规则,不能够直接检索 URL。但是,根据 ISBN 可以找到图书的详细页面,可以遍历图书详细页面的 HTML 以找到图像的 URL:
public class FindBook
{
public string GetWebPageAsString(string url)
{
WebRequest requestHtml = WebRequest.Create(url);
WebResponse responseHtml = requestHtml.GetResponse();
StreamReader r = new StreamReader(responseHtml.GetResponseStream());
string htmlContent = r.ReadToEnd();
r.Close();
return htmlContent;
}
// Amazon 的图像 URL 大多使用如下的格式
// http://ec1.images-amazon.com/I/[ImageName].jpg
public string GetImageUrl(string isbn)
{
try
{
isbn = isbn.Replace("-", "");
string bookUrl = "http://www.amazon.com/exec/obidos/ASIN/" + isbn;
string bookHtml = GetWebPageAsString(bookUrl);
string imgTagPattern = "<img src=\"(http://ecx.images-amazon.com/images/I/[^\"]+)\"";
Match imgTagMatch = Regex.Match(bookHtml, imgTagPattern);
return imgTagMatch.Groups[1].Value;
}
catch
{
return "";
}
}
}
现在运行程序,默认图片会首先出现,接着程序会后台获取真正的图片,拿到正确的地址后会逐一替换默认图片。
不过,类似这样的处理,使用专门的 Amazon Web 服务显然是更灵活且更健壮的做法。但是它不会改变这个例子,这个例子演示了使用 js 对性能的提升。