AJAX 设计模式

来源:互联网 发布:建筑业企业网络快报 编辑:程序博客网 时间:2024/05/14 14:01
 
AJAX 设计模式
原名:AJAX Design Patterns
摘自http://snyke.net 由Snyke发表
Fishbone译
我想到现在为止,没听说过AJAX的人应该为数不多了吧,甚至没有关注过web开发的人也能感受到新技术的潜力。大家一定已经看烦了那些关于AJAX的介绍文章,介绍AJAX是多么神奇,并拿GOOGLE SUGGEST,GMAIL等等技术来说明AJAX的前途,因此我就不再重复介绍AJAX的发展历史等等了,而直接说说在真实世界里该怎么去应用它。
那么我这里所讲的文章跟其他的有什么不同呢?不管是与否,我这里所讲的不是利用AJAX创建一些奇特的小程序,而是讲讲怎么用AJAX来设计和开发一个完整的网站。为了增加文章的可读性,避免使用一些可读性较差的底层代码,我决定用dojo的开发工具toolkit来讲,我也曾经打算采用prototype的js库来讲,因为它写的很好,而且简单易用,但是dojo给我们提供了更多封装好的功能。无论哪种框架,都有一个共同的缺陷:缺少详细的说明文档,我们需要花费很多时间在调试上或在阅读一些解读它代码的文章来帮助理解。
对于调试,我建议用firefox浏览器的自带调试工具来调试,它真的是一个不错的调试工具,它能让AJAX更容易理解,特别是它的调试日志记录了每个AJAX的请求信息。
本文章,我们将利用AJAX来设计一些组件做为我们交流的入口,当然也得设计一套基本的开发工具,以帮助我们开发更加复杂的程序。
我们想要什么?
做为一个开发者,我们需要在开始开发之前知道我们要怎么去做。需求分析不是本文章的一部分,所以我们只讲设计模式的一些基本原则:
对于页面:加载和显示的HTML代码越简单越规范越好。
对于用户:我们需要很好的交互性,所以让用户自己注册,建立资料,并能和其他用户交互这是最基本的功能。
对于消息:有些能及时收发消息的web通讯做的很不错,运行起来也简单,而且他可以实现用户和用户只间的交互。
论坛:要交流技术或者其他什么话题,那么没有比论坛更合适的了。
很多功能将在以后加上去,但是对于现在,上述的这些功能就暂时能满足当前的需求了。
页面设计
虽然说我们的重点应该放在开发功能上面,但是设计对于程序来说还是同等重要的。如果一个程序写的很好,但是却没有很好的交互性,那么它也就没什么用处了。我们这里将设计一个简单的页面:
1. 内容区域
2. 边缘导航菜单
3. 主菜单(各个组件之间的调用都是建立在这个主菜单之上的)
4. 页面标题,没什么好说的
5. 在线用户列表
组件
以AJAX为基础框架的话,我们就可以摆脱老页面的一时一页的模式,来谈谈多事件模式或者MVC-模式。
我们可以自定义一些API方法,这些方法可以调用动态语言与后台进行交互。我们可以调用这些API方法进行一些复杂的任务。
回到本文章上,我们将用几个小的组件来布置网页,以后我们会结合虚拟组件(virtual modules)来实现一些功能。这些组件包括:
页面组件:这是我们运行的第一个组件,这里只是显示从文件取过来的内容,没什么其他特别的。
用户组件:这一组件,能完成不同的任务
       注册:注册一新用户
       登录:
       资料:显示用户的相关信息。
在线显示:一旦发现有用户上线,就启动控件显示上线的用户信息,而用AJAX来实现这一功能,它的效果几乎是同步的,就是说一有用户上线就马上提示的,而不是延时多少时间才显示出来的。而且它的效率很高,运行简单。
       论坛:还有什么比论坛的交互性更好的网站?
代码的执行
我们将我们要用到的功能都封装到一个对象内,这很类似于java里的静态类,这样做的话,我们就可以将不同组件里的命名较清晰地分离开来。首先我们得创建一个引擎。载入其他组件以及使他们跟页面的交互都是通过引擎来处理的。然而在页面交互(包括规划,设计,……)和引擎的背后,数据的呈现和输出我们将利用层来做,因为它运行起来简单,所以不必考虑其他的HTML对象了。换句话说,我们是在开发应用程序界面。
首先要做的也是最重要的是,引擎得先将一切初始化。
var Engine = {
 bootstrap: function(){
    this.setStatus(“Loading…”);
    dojo.require(“dojo.io.*”);
    dojo.hostenv.writeIncludes();
    if((“”+document.location).indexOf(‘#’) < 0){
      Engine.loadPage(“#Page/Welcome”);
    }else{
      Engine.loadPage(“” + document.location);
    }
 },
这部分代码我想是很容易理解的,它设置了一个进度状态提示信息"loading……",还包含了一些dojo的库,然后用Engine.loadPage()来显示页面。如你所见,他是将所有的东西都封装在一个对象"Engine"里面,这样的话,外部的程序很容易调用它。页面载入完成时执行的函数是这样写的:
<body onload=“Engine.bootstrap()”></body>
接下来该做的事就是让这个Engine能够载入其他组件,它的完成是通过载入一个包含组件的javascript文件(再次将它自己的命名空间封装)来完成的,通过调用一个组件的init()方法我们就可以初始化这个组件了。
 loadedModules: new Array(),
 modules: new Array(),
  loadModule: function(module){
    if(Engine.loadedModules[module])
      return;
    dojo.io.bind({
      url: “javascript/” + module + “.js”,
      sync: true,
      error: function(type, evaldObj){Engine.showError(“Error while loading module.”);},
      load: function(type, data, evt){
        eval(data);
        Engine.modules[module].execute(uri);
      }
    });
 },
在性能上考虑,我们希望组件被装载一次就够了,这就是为什么我们在第1-2行用了两个数组的缘故:loadedModules[] 这个数组包含了一组布尔型的数据,它是用来告诉我们每个组件是不是已经被载入,第二个数组只是包含了一些对组件的引用(作为一个对象,他们可以这样来引用).loadModules(),其实它本身没什么其他东西,就是一个同步的xmlhttprequest来获取组件的代码功能。因为我们不想让代码没下载完就被调用,所以我们这里是同步的,这样能保证调用都能成功。我们也已经注意到了,代码是在eval()里运行的。
现在我们把目光放在loadPage()这个函数上。通过取得一个输入的URI(即js文件的路径),他就能通过控制一个已经载入的组件来载入相应的组件,得到想要的结果:
 uri: “/”;,
  loadPage: function(url){
    Engine.setStatus(“Loading…”);
    if(!url)
      url = “” + document.location;
    var hashIndex = url.indexOf(‘#’);
    if(hashIndex < 0 || hashIndex <= url.length-2)
      return Engine.hideStatus;
    uri = url.substring(hashIndex + 1);
    var moduleLength;
    if(uri.indexOf(‘/’) > 0)
      moduleLength = uri.indexOf(‘/’);
    else
      moduleLength = uri.length;
    var module = uri.substring(0,moduleLength);
    uri = uri.substring(uri.indexOf(‘/’));
    if(Engine.loadedModules[module] && ! dojo.lang.isUndefined(Engine.modules[module])){
      Engine.modules[module].execute(uri);
    }else{
      Engine.loadModule(module);
    }
 },
URI已经载入,那么以后其他的组件要调用他就没必要再去载入它了。我们可以发先其实loadPage()这个函数所做的主要工作是解析URL地址。通过第一个参数来判断哪一个组件需要被载入是相当简单的。也许很多人会问,为什么我们得用象这样的"http://www.example.com/Page#This/is/a/long/string" URL地址呢?那是因为我们我们不想丢弃标签在页面中的作用。AJAX本身其实抛弃了书签的作用,因为它的所读取的信息和展现的信息都是在一个页面中的,然而如果不是AJAX的话,每一个URL地址其实都是独立的一个资源。我们通过在URL地址的最后面添加#以及一些额外数据的方法来使得浏览器认为我们是在访问新的网址,这样的话,AJAX会重新装载页面。bootstrap()这个函数其实也是通过传递URL给loadPage()来载入页面的。简而言之:当用户第二次访问我们的网站时,只要输入URL地址就可以看到,我们访问过的URL页面都还在历史记录里保留着。
URL的解析步骤:
1.‘#’前面的所有字符是程序的地址
2.‘#’和第一个‘/’之间的是组件名,如果它是存在的就会被载入
3.‘/’到URL地址的结束之间的字符作为组件中的调用函数execute()里的参数(可以参见下面的例子)
那么我们现在能做的就是运行一些将在后面组件中用到的示例性的函数:
setStatus: function(message){
    if($(’status’) != null){
      $(’status’).parentNode.removeChild($(’status’));
    }
    var body = document.getElementsByTagName(“body”)[0];
    var div = document.createElement(“div”);
    div.style.position = “absolute”;
    div.style.top = “50%”;
    div.style.left = “50%”;
    div.style.width = “200px”;
    div.style.margin = “-12px 0 0 -100px”;
    div.style.border = “0px”;
    div.style.padding = “20px”;
    div.style.opacity = “0.85″;
    div.style.backgroundColor = “#353555″;
    div.style.border = “1px solid #CFCFFF”;
    div.style.color = “#CFCFFF”;
    div.style.fontSize = “25px”;
    div.style.textAlign = “center”;
    div.id = ’status’;
    body.appendChild(div);
    div.innerHTML = message;
 },
 
 hideStatus: function(){
    Engine.opacityDown($(’status’));
 },
 
 opacityDown: function(theElement){
    if(theElement == null)
      return;
    var opacity = parseFloat(theElement.style.opacity);
    if (opacity < 0.08){
     theElement.parentNode.removeChild(theElement);
    }else{
      opacity -= 0.07;
      theElement.style.opacity = opacity;
      setTimeout(function(){Engine.opacityDown(theElement);}, 50);
    }
    return true;
 },
 setContent: function(content){
    $(‘content’).innerHTML = content;
 },
 
 showError: function(message){
    Engine.setStatus(message);
    setTimeout(“Engine.hideStatus()”,10000);
 }
}
到此为止,引擎已经讲完了,完整代码可以在
http://www.snyke.net/AjaxTutorial/stage1/javascript/Engine.js下载到
第一个组件
那么我们现在就来运行我们的第一个组件。他的任务是通过异步的方式从外部载入一个额外的资源(一个HTML页面)然后在我们的内容区域显示出来。
var Page = {
 init: function(){
    Engine.modules[“Page”] = Page;
    Engine.loadedModules[“Page”] = true;
 },
  execute: function(uri){
    try{
      dojo.io.bind({
        url: “resources” + uri + “.php”,
        sync: false,
        error: function(type, evaldObj){
          Engine.showError(“Error while loading Content.”);},
        load: function(type, data, evt){
          Engine.setContent(data);
          Engine.hideStatus();
      });
    }catch(e){
      alert(e);
    }
 }
}
Page.init();
当组件被载入,他会到引擎里先注册自己(参见init()函数)然后引擎会调用execute()方法异步取得页面资源,然后将它在内容区域显示出来。是不是很简单?
但是你也可以发现,我们也可以用它来做很复杂的组件,我们将在文章的下一部分讲述如何用组件来做一个论坛。
上面的页面资源载入的完整代码可以在
http://www.snyke.net/AjaxTutorial/stage1/javascript/Page.js下载到
总结
你也可以看一下http://www.snyke.net/AjaxTutorial/stage1/MainPage.php或者下载整个源代码http://www.snyke.net/AjaxTutorial/stage1.tar.gz来分析。我希望这篇文章能对你有用,能帮助你理解怎么去设计你的web程序。下一部分我们将讨论在线显示问题。