论坛首页 Web前端技术论坛

AJAX表格分页模板:探讨基于Prototype框架的javascript面向对象设计(中)

浏览 18417 次
该帖已经被评为良好帖
作者 正文
   发表时间:2007-05-16  
        在上一篇文章中,我们用静态页面的方式,设计出了分页模板的表现形式,接下来让我们利用javascript这个强大的操盘手来粘合其余的部分吧。

第一步,我们需要设计一个抽象的基类,来实现代码复用(js的OO,不就是为了这个么,还有就是方便管理代码而已)。
        首先,我们搞个命名空间来管理基类及其子类:
js 代码
  1. var Tbi = new Object();    //全局命名空间  
       
        为啥叫“Tbi”?就不要问了,项目组的名称而已,呵呵:)接下来考虑我们的模板基类,他有什么特征呢?
        1、需要持有一个到页面容器的引用,以便将自身加入页面流;
        2、需要保存用户设定的每页显示条目数;
        3、需要保存获取分页数据的服务器端地址(URL);
        这些成员可以通过构造器来初始化,除此之外,为了避免数据更新发生混乱(这个在异步非缓存的时候需要注意),还要有一个全局异步对象来获取数据,但不需要通过参数提供。
        定义了成员变量,模板对象还需要有可操作的方法:
        1、构造函数:initialize,这个方法在构建Prototype风格构建类,实例化对象的时候会自动调用。另外有一些技巧下面会提到;
        2、在数据获取过程中,显示等待信息的方法:_showMessage,这里只是简单显示一句话,可以按需替换成显示图片等;
        3、显示模板初始页面的show方法(其中有两个故意设计的事件入口,供今后扩展使用):
                A、调用beforeShow,默认为空方法,什么也不做,可按需设定;
                B、调用_display方法,显示模板初始页面:
                        a、调用2中的方法显示等待信息;
                        b、调用_catchContent方法异步获取服务器数据。此方法可由所有模板共用,因此放到基类中达到代码重用。可                        以看到, 我们为了细粒度的控制Prototype的ajax封装,使用了transport属性。
                        c、在成功返回服务器数据之后,addContent方法将把分页内容添加到容器中,分两步来做:
                                 (1) addPage方法作为回调函数添加模板主体——抽象方法,由子类实现。
                                 (2) addNavigation方法根据实际情况生成并添加分页导航——抽象方法,我将它在一个子类中实现然后演示
                                 混入(mixin),其实完全可以在基类中提供达到同样的代码复用效果。
js 代码
 
  1. /* 
  2.  * 模板基类 
  3.  */  
  4. Tbi.Template = Class.create();  
  5. Tbi.Template.prototype = {  
  6.     initialize: function(){  
  7.         this._init.apply(this,arguments);  
  8.     },  
  9.       
  10.     _init: function(wrap,items,catchUrl) {  
  11.         this.wrap = $(wrap);  
  12.         this.items = items;  
  13.         this.catchUrl = catchUrl;  
  14.         this.ajax = null;   //全局异步对象  
  15.     },  
  16.       
  17.     // 显示等待信息  
  18.     _showMessage: function(text){  
  19.         this.wrap.innerHTML =  text;  
  20.     },  
  21.       
  22.     // 显示模板默认页面  
  23.     show: function(){  
  24.         this.beforeShow(); //显示前事件处理入口  
  25.         this._display();     
  26.         this.afterShow(); //显示后时间处理入口  
  27.     },  
  28.       
  29.     // 显示前事件处理入口  
  30.     beforeShow: function(){},  
  31.       
  32.     _display: function(){  
  33.         this._showMessage("正在获取数据,请稍等……");  
  34.         this._catchContent();  
  35.     },  
  36.       
  37.     //取得默认页面内容,由两个模板公用  
  38.     _catchContent: function(){  
  39.         if(this.ajax){  
  40.             this.ajax.transport.abort();  
  41.         }  
  42.         this.ajax = new Ajax.Request(  
  43.             this.catchUrl,  
  44.             {  
  45.                 method: "get",  
  46.                 parameters: {"mode":this.mode,"items":this.items},  
  47.                 onComplete: this.addContent.bind(this)  
  48.             }  
  49.         );  
  50.     },  
  51.       
  52.     // 添加页面内容  
  53.     addContent: function(xmlhttp){  
  54.         this.addPage(xmlhttp);          //抽象方法,添加页面内容  
  55.         this.addNavigation();           //抽象方法,添加分页导航  
  56.     },  
  57.       
  58.     // 显示后事件处理入口          
  59.     afterShow: function(){}  
  60. }  

一些技巧:
        为了实现在子类中覆盖initialize构造函数,我们只需要将基类的成员初始化工作委托给_init方法即可,下面会看到他的作用。
        模仿事件处理机制,我们留下了两个事件入口beforeShow和afterShow,分别可以设置显示前事前和显示后事件。
        Prototype为Function对象扩展了一个bind方法和bindAsEventListener方法,可以很方便的将函数上下文(this)切换为别的对象,这里,我们切换为模板对象。
bind和bindAsEventListener的功用相似,但是有一点区别,用bind切换的方法,如果有自动传入的参数,比如事件对象event,那么这个参数将被自动传入函数参数列表的最后,而后者则是自动传入函数参数列表的最前面,这个技巧很常用。比如改写一下上面的代码,我给onComplete事件传入一个自定义参数tmyArg,使用两种不同的绑定方法(xmlhttp是Prototype自动传入回调函数的参数):
js 代码
  1. //用bind方法  
  2. this.addContent.bind(this,myArg)  
  3. //调用方式  
  4. this.addContent(myArg,xmlhttp)  
  5.   
  6.   
  7. //用bindAsEventListener方法  
  8. this.addContent.bindAsEventListener(this,myArg)  
  9. //调用方式  
  10. this.addContent(xmlhttp,myArg) 

第二步,实现静态模板类StaticTemplate。对于静态模板,其显示过程是这样的:向服务器发起一次异步请求,返回所有分页数据,服务器按照客户设定的每页显示数量来生成所有页,一次新传回客户端。每一页由一对tbody元素定义,然后通过css类名来讲所有的页面隐藏,最后由客户端js来控制显示页面,初始显示第一页。服务器返回的数据格式如下(只要结构一样就可以了,没有行、列数目的限制,这可以参看前一篇文章中的css设定了解,是可以随表格大小伸缩的),总条目数为6,分了两页:
xml 代码
 
  1. <table id='pages'>      
  2.     <caption>静态分页模板</caption>      
  3.     <thead>      
  4.         <tr>      
  5.             <th>标题一</th>      
  6.             <th>标题二</th>      
  7.             <th>标题三</th>      
  8.             <th>标题四</th>      
  9.             <th>标题五</th>      
  10.             <th>标题六</th>      
  11.         </tr>      
  12.     </thead>      
  13.     <tbody class='hidden'>      
  14.         <tr>      
  15.             <td>1</td>      
  16.             <td>1</td>      
  17.             <td>1</td>      
  18.             <td>1</td>      
  19.             <td>1</td>      
  20.             <td>1</td>      
  21.         </tr>      
  22.         <tr>      
  23.             <td>2</td>      
  24.             <td>2</td>      
  25.             <td>2</td>      
  26.             <td>2</td>      
  27.             <td>2</td>      
  28.             <td>2</td>      
  29.         </tr>      
  30.         <tr>      
  31.             <td>3</td>      
  32.             <td>3</td>      
  33.             <td>3</td>      
  34.             <td>3</td>      
  35.             <td>3</td>      
  36.             <td>3</td>      
  37.         </tr>      
  38.         <tr>      
  39.             <td>4</td>      
  40.             <td>4</td>      
  41.             <td>4</td>      
  42.             <td>4</td>      
  43.             <td>4</td>      
  44.             <td>4</td>      
  45.         </tr>      
  46.         </tbody>      
  47.           
  48.         <tbody class='hidden'>      
  49.         <tr>      
  50.             <td>5</td>      
  51.             <td>5</td>     
  52.             <td>5</td>      
  53.             <td>5</td>      
  54.             <td>5</td>      
  55.             <td>5</td>      
  56.         </tr>      
  57.         <tr>      
  58.             <td>6</td>      
  59.             <td>6</td>      
  60.             <td>6</td>      
  61.             <td>6</td>      
  62.             <td>6</td>      
  63.             <td>6</td>      
  64.         </tr>      
  65.     </tbody>      
  66. </table>   


        StaticTemplate类中,addPage方法统计table中tbody元素的个数来确定总页数,并附加到对象上。以供
addNavigation方法成分页导航信息:
js 代码
  1. this.pageTotal = this.wrap.getElementsByTagName("tbody").length;  

        在addNavigation方法中,又一次用到了前面提过的bindAsEventListener
技巧,并通过切换css类名来达到突出显示当前页码以及显示页面。

具体的
StaticTemplate代码实现如下:
js 代码
 
  1. /* 
  2.  * 静态模板,一次请求全部数据 
  3.  */  
  4. Tbi.StaticTemplate = Class.create();  
  5. Tbi.StaticTemplate.prototype = Object.extend(new Tbi.Template(),  
  6.     {  
  7.         /* 
  8.          * 构造函数,实例化静态模板 
  9.          * 参数:父容器,每页显示条目数,数据获取地址
  10.          */  
  11.         initialize: function(wrap,items,catchUrl){  
  12.             this._init(wrap,items,catchUrl);  
  13.             this.mode = "static";  
  14.         },  
  15.           
  16.         // 实现父类抽象方法,向页面添加默认分页  
  17.         addPage: function(xmlhttp){  
  18.             this.wrap.innerHTML = xmlhttp.responseText;  
  19.             this.pageTotal = this.wrap.getElementsByTagName("tbody").length;  
  20.             var table = $("pages");  
  21.             var pages = $A(table.getElementsByTagName("tbody"));  
  22.             displayPage = pages[0];  
  23.             displayPage.className = "";  
  24.         },  
  25.           
  26.         // 实现父类抽象方法,向页面添加分页导航  
  27.         addNavigation: function(){  
  28.             if(this.pageTotal>1){  
  29.                 var navigation = document.createElement("div");  
  30.                 navigation.id = "navigation";  
  31.                 var context = this;  
  32.                 $R(1,this.pageTotal,false).each(  
  33.                     function(item){  
  34.                         var link = document.createElement("a");  
  35.                         link.href = "#";  
  36.                         link.onclick = context._changePage.bindAsEventListener(context,link);  
  37.                         link.appendChild(document.createTextNode(item));  
  38.                         if(item==1){  
  39.                             link.className = "active";  
  40.                         }  
  41.                         navigation.appendChild(link);  
  42.                     }  
  43.                 );  
  44.                 this.wrap.appendChild(navigation);    
  45.             }             
  46.         },  
  47.           
  48.         // 导航链接点击事件处理函数,切换页面内容,同时改变导航链接样式(突出显示当前页)  
  49.         _changePage: function(event,link){  
  50.             var activeLink = $$('#TMPwrap div a[class="active"]')[0];  
  51.             if(activeLink != link){  
  52.                 var pages = $$("#TMPwrap tbody");  
  53.                 var oldPage = pages.find(  
  54.                     function(item){  
  55.                         return item.className == "";  
  56.                     }  
  57.                 );  
  58.                 var newPage = pages[link.firstChild.nodeValue-1]; //inner系列属性不兼容ff
  59.                 oldPage.className = "hidden";  
  60.                 newPage.className = "";  
  61.                 activeLink.className = "";  
  62.                 link.className = "active";  
  63.             }  
  64.             Event.stop(event);    
  65.         }  
  66.     }  
  67. );  

这样,我们就完成了静态分页模板的设计和实现,下一步是实现动态异步的两种获取方式模板。

接下来的内容,请参看:

AJAX表格分页模板:探讨基于Prototype框架的javascript面向对象设计(下)


   发表时间:2007-05-16  
抱怨一句,je的代码编辑工具真的是太难用了啊,老是显示有问题,怎么回事?好影响心情,为了正确显示html代码,我都改了无数的次了,不知道是什么bug……
0 请登录后投票
   发表时间:2007-05-16  
不错的学习示例
0 请登录后投票
   发表时间:2007-05-16  
笨笨狗 写道
抱怨一句,je的代码编辑工具真的是太难用了啊,老是显示有问题,怎么回事?好影响心情,为了正确显示html代码,我都改了无数的次了,不知道是什么bug……


推荐帖子里面简要介绍下内容,具体代码作为附件上传,省时省力,呵呵。
0 请登录后投票
   发表时间:2007-05-16  
呵呵,有bug可以给ouc jj报告呀。
0 请登录后投票
   发表时间:2007-05-29  
我觉得你写的代码太复杂了,当然如果是为了功能强大,那就没什么好说的了  你的支持DWR吗?
我个人认为JS代码 越简单越好 因为JS代码可维护性太低了
0 请登录后投票
   发表时间:2007-05-29  
to jiangzhen1984:

之所以这么写,就是为了探讨Prototype风格的OO特性。其实如此包装的话,是可以提高可维护性的,增加命名空间,封装成类、对象。

dwr,个人对那种数据通信方式不是很感冒。相反,自己定义好数据传输格式,可以更灵活,比如支持REST架构。
0 请登录后投票
   发表时间:2007-05-29  
在OO的包装方面,我觉得mootools挺不错的
0 请登录后投票
论坛首页 Web前端技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics