首页 | PHP资讯 | 技术专栏 | 资源共享 | PHP培训 | PHP职场 | 图书 | PHP ON WIN | PHP圈子 | PHPer学习大本营
返回列表 回复 发帖

[原创]Zend Framework中Ajax的一个使用技巧

[原创]Zend Framework中Ajax的一个使用技巧

前言:其实这个技巧,不限于Zend Framework。我对于这个技巧,是来自Ruby On Rails的学习过程中,在Thomas的书中有提到这种ajax场景;而本文章主要讲述这个技巧在Zend Framework中如何实现。

几点说明:
1、本文章的Js部分,采用jQuery;我比较中意这个。但是本文章侧重讲的是一个思路;而不是js的写法技巧。
2、阅读本文章前,最好对Zend Framework了解;并了解Zend_Layout的概念。


一个场景的处理思考

先从一个场景来入手。假设你有一个请求时http://project-name/product/list,来显示商品列表。而,普通情况下,你的这个请求来自左侧菜单栏;请求方式是一个ajax请求;即不更新菜单栏以及header footer,只更新在<divid='mainbody'></div>的内容。

这是一个常见的ajax应用场景。

一般情况下,是没什么问题。但是有一天一个聪明的用户知道这个url(知道方法很多),而直接在浏览器上输入http://project-name/product/list,那么结果呢?
你显示了一块不带有header footer 的html代码,你的界面里没有定义css,因此显示很难看;没有js,可能很多点击无效无法工作。

用户不懂这些,他只知道出现error。

这个情况,你遇到过吗?我的应用中,其实就遇到过;之前的解决办法没有,只是希望用户别这么聪明。

好了,问题明白了;解决之前,我们先一起看看Zend_Layout的使用方法。
更多的Zend_Layout的学习,可以先阅读 http://akrabat.com/2007/12/11/simple-zend_layout-example/的内容 以及官方文档 http://framework.zend.com/manual/en/zend.layout.html

首先建立一个普通的Zend_Layout使用例。

    private function _buildMVCLayout(){
        //增加layout
        //$this->_config 是一个Zend_Config_Xml类;内容来自一个配置文件。
        if(!empty($this->_config->view->layout->enable)){
            $_viewPath = trim($this->_config->view->layout->path);
            $_viewPath = empty($_viewPath)?'layout':$_viewPath;            
            Zend_Layout::startMvc(array('layoutPath'=>APP_DIR.DIRECTORY_SEPARATOR.$_viewPath));        
        }
    }

那么,在layout的目录下,建立一个lauout.phtml
[php]
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
  <meta http-equiv="content-type" c />
  <meta name="description" c />
  <meta name="keywords" c />
  <link rel="icon" type="image/x-icon" href="<?php echo $this->baseUrl; ?>/public/images/layout/favicon.ico" />
  <title><?php   echo empty ( $this->title ) ? $this->translate ( 'meta.title.default' ) : $this->escape ( $this->title ); ?></title>
  <?php
    echo $this->loadCss ( array ('layout' ), $this->baseUrl );
    echo $this->loadJs ( array ('jquery-1.2.1.pack', 'loading', 'jquery/jquery.extend', 'jquery/jquery.action'), $this->baseUrl );   
  ?>   
</head>
<!-- Global IE fix to avoid layout crash when single word size wider than column width -->
<!--[if IE]><style type="text/css"> body {word-wrap: break-word;}</style><![endif]-->
<body>  
  <div class="page-container">     
    <div class="header">  
      <div class="header-top">
          <img class="loading_position" style="display:none" id="loading" src="<?php echo $this->baseUrl; ?>/public/images/icons/loading-large.gif" />
        <?php echo $this->layout()->header_top; ?>   
        <?php echo $this->layout()->header_nav; ?>               
      </div>
    </div>  
    <div class="main">      
      <div class="main-navigation">        
        <?php echo $this->layout()->sidebar_menu; ?>               
      </div>
      <div class="main-content" id="mainbody">         
          <?php echo $this->layout()->content ?>         
      </div>
    </div>
    <div class="footer">
      <p>Copyright © 2006 qqinxl | All Rights Reserved</p><p class="credits">Design by <a href="http://www.1-2-3-4.info/" title="Designer Homepage">Wolfgang</a> | Modified by <a href="#" title="Adaptor Homepage">qqinxl</a> </p>
    </div>      
  </div>   
</body>
</html>
[/php]

这是一个典型的layout布局;其实该模板来自http://www.1-2-3-4.info/ 。利用$this->translate() 可以做到Zend_View内国际化显示;主要内容是在$this->layout()->content内显示;即在<div id="mainbody"></div>内显示(这个id名字很关键)。

用户如果请求一个url为http://project-name/product/list,那么Zend_Framework首先执行ProductController->listAction() ,渲染product/list.phtml,然后填充到layout.phtml内。

这里开始思考一个Ajax场景。

假设存在两个连接(比如在左侧的菜单栏内),一个发出的是Ajax请求,一个是普通的link连接

//Ajax请求
<a id="link1" href="#"><?php  echo $this->translate ( 'menu.product.list' ) ;?></a>
//普通的link连接
<a id="link2" href="<?php echo  $this->baseUrl;?>/product/list"><?php echo $this->translate ( 'menu.product.list' ) ;?></a>
<script type="text/javascript">   
//onclick 事件在这里
//尽量html和js分离   
$(document).ready(function() {   
        $("#link1").click(function () {   
                var url = '<?php  echo  $this->baseUrl;?>/product/list';      
                $.ajaxRequest('mainbody',url);            
                return false;
            });
});
</script>

$.ajaxRequest就是发出一个url请求(参数2),执行后更新div名为mainbody(参数1)的内容。这个js代码后面给出。

一个希望的结果是,如果发出的是link连接(id=link2),当然是整个页面刷新,当然希望除了显示product/list.phtml 还是渲染layout.phtml的内容。
而如果发出的是ajax请求(id=link1),应该关闭layout.phtml;只显示list.phtml内容。

如果这个问题解决了,很明显,第一部分中,我提到的那个聪明的用户直接输入url请求的问题也随着解决了。其实,用户直接输入就是一个普通link请求。

利用Zend Framework 实现

我们只需要在继承Zend_Controller_Action的时候增加如下代码:

public function preDispatch(){      
        //MVC        
        if ($this->getRequest ()->isXmlHttpRequest ()) {
            $this->getHelper ( 'layout' )->disableLayout ();
        }else{   
            $response = $this->getResponse ();
            $response->insert ( 'header_top', $this->view->render ( 'basic/header.top.phtml' ) );
            if (empty ( $this->_loginUser ) or ! $this->_loginUser instanceof Object_User) {
                $response->insert ( 'sidebar_menu', $this->view->render ( 'basic/sidebar.menu.no.login.phtml' ) );
            } else {
                $response->insert ( 'header_nav', $this->view->render ( 'basic/header.nav.phtml' ) );
                $response->insert ( 'sidebar_menu', $this->view->render ( 'basic/sidebar.menu.phtml' ) );
            }
        }        
    }   


看到了没有 ,$this->getRequest ()->isXmlHttpRequest () 这是一个判断ajax请求的函数。

   //Zend_Controller_Request_Http类内
   /**
     * Is the request a Javascript XMLHttpRequest?
     *
     * Should work with Prototype/Script.aculo.us, possibly others.
     *
     * @return boolean
     */
    public function isXmlHttpRequest()   {
        return ($this->getHeader('X_REQUESTED_WITH') == 'XMLHttpRequest');
    }

官方解释在这里 http://framework.zend.com/manual ... r.request.http.ajax
当然 jQuery 支持这一函数的判断。

如果是ajax请求 通过 $this->getHelper ( 'layout' )->disableLayout (); 关闭layout
否则 渲染lauout的内容;在例子中,我还通过是否存在 当前登录用户loginUser,来渲染不同左侧菜单栏sidebar_menu。

这是一个非常有用的技巧。

另外一个应用

$this->getRequest ()->isXmlHttpRequest () 经常还应用在另外一个ajax场景内。

比如,我们提交一个表单;我们希望通过ajax提交,并将结果显示在一个叫<div id=fm-intro></div>内;而不是刷新整个页面。
一个常见的写法是:

$this->view->title = $this->_ ( 'product.new');
//产生一个form
$url = $this->_baseUrl . '/' . $this->_pageInfo->getModuleUrl() . 'product/new';
$form = new Form_Product_New( );
$form->setAttrib ( 'action', $url );
$this->_formData = null;        
if ($this->getRequest ()->isPost ()) {
      $this->getActionController ()->getHelper ( 'viewRenderer' )->setNoRender ();
      $this->_formData = $this->getRequest()->getPosts ();
      if ($this->_form->isValid ( $this->_formData )) {
            //注册
           $module = Module_Product::getInstance();
           $check = $module->insert($this->_formData);
           if (empty ( $check )) {
              //失败
              $this->view->errors = $this->_module->translateError ();
              $this->view->message = $this->_ ( ‘product.fail’);
            } else {     
               //成功               
               $this->view->message = $this->_ ( 'product.success', $options );
            }
      } else {
           //注册前验证失败
           $this->view->message = $this->_ ( 'product.check');
           $this->view->errors = $this->_form->translateError ();
      }
      $this->render('message');
      return;
}
//如果没有post数据,显示原始form
$this->view->form = $this->_form;
$this->render ();


这种写法 是没问题的;缺点和上面一样,在浏览器js失效的情况下,无法正确显示页面。因此,一个好的写法是

// $this->render ( 'message' );的地方增加判断
if ($this->getRequest ()->isXmlHttpRequest ()) {
      $this->render ( 'message' );
} else {
       $this->render ();
}


更进一步

更进一步,我们在页面显示部分可以这么做;我们在制作sidebar_menu的时候,完全不考虑ajax;首先生成普通的link连接。

<h1 class="first"><?php echo $this->translate ( 'sidebar.menu.user.title' );?></h1>
<!-- Navigation with bullets -->
<dl class="nav3-bullet" id="sidebar_menu">
    <dt>
        <a href="<?php echo $this->baseUrl;?>/product/list">
            <?php echo $this->translate ( 'sidebar.menu.products.list' );?>
        </a>
    </dt>
    <dt>
        <a href="<?php echo $this->baseUrl;?>/product/register">
            <?php echo $this->translate ( 'sidebar.menu.products.new' );?>
        </a>
    </dt>   
    <dt>
        <a href="<?php echo $this->baseUrl;?>/auth/logout">
            <?php echo $this->translate ( 'sidebar.menu.logout' );?>
        </a>
    </dt>
</dl>


很明显,根据上面的设置,这些普通连接都可以正常工作;只是每次都需要刷新整个页面。

为了加速响应速度,采用ajax请求;一个笨方法是,去修改每一个连接,将href的值变成"#",增加一个onclick请求,写一段js代码。
这样做,除了麻烦之外,一个明显的缺点是,如果用户浏览器不支持js,那么这些连接全部无效。

一个简单的办法如下:不修改原来的html任何东西,只是将普通连接转化为ajax连接
[php]
<script type="text/javascript">        
$(document).ready(function() {   
    $("#sidebar_menu a[@href!='']").toAjaxLink();   
});
</script>
[/php]
那么toAjaxlink()方法呢?如下代码:

//jquery.action.js
//toAjaxLink()函数目前参数只有一个toId。其实可以扩展很多东西,比如点击后首先加载某些js css 然后在ajax请求。
(function ($) {   
    $.fn.toAjaxLink = function (options) {        
        return this.each(function () {   
            options = $.extend({   
                    toId:"mainbody"                    
                }, options || {});                    
            $(this).attr("id",$(this).attr("href")).attr("href","#");                                
            $(this).click(function () {   
                var url = $(this).attr("id");                                   
                $.ajaxRequest(options.toId,url);            
                return false;
            });
        });
    };
})(jQuery);
//下面是ajaxRequest的代码
//jquery.extend.js
(function($) {   
    $.extend({
        ajaxRequest: function(toId,url,pars,type,evalScript) {                     
            $.ajax({
                url: url,
                type: (type === null || type === '')?'GET':type,
                dataType: 'html',
                data: pars,
                evalScripts: (evalScript === null || evalScript === '' || evalScript === false)?false:true,
                error: function(){
                   //error Function
                },
                success: function(html){                    
                    $('#'+toId).html(html);
                }
            });
            return false;
        }   
})(jQuery);


如果用户浏览器js不执行,也就是$("#sidebar_menu a[@href!='']").toAjaxLink();    不工作了,那么连接还是原来的普通连接,一切不变;
那么如果执行 $("#sidebar_menu a[@href!='']").toAjaxLink();   ,根据定义连接变成一个ajax连接,请求变成ajax请求。

其实这个技巧,和Zend  Framework没有任何关系;可以应用在很多地方。最重要的这种思路解决浏览器端js不支持的问题。而且还减轻服务器端代码量;我们几乎不需要考虑ajax情况怎么办 非ajax怎么办。比如很多分页类 都有isAjaxLink之类的参数。生成一个ajax请求的分页连接;这种思考增加了服务器端代码工作量。


思路总结:
1、连接请求,是否是ajax请求,还是普通link请求,不是在服务器端设定;而是在客户端变换。
   
2、请求内容的返回,服务器端可以根据ajax请求,还是普通link请求,来做不同处理。

好了,现在这个聪明的用户直接在浏览器上输入http://project-name/product/list,Ok 没问题,让他自由输入去~~

[ 本帖最后由 qqinxl 于 2008-3-31 16:46 编辑 ]

不错,认真学习下!
人不能没有奋斗的目标,尽管有的时候你说不清楚你的目标是什么,但在你的心中一定得有一样让你始终坚持追求的东西,这样你才会活得充实,更有意义

TOP

精品啊!

TOP

qqinxl果然厉害 :titter:

TOP

靠 半年前的帖子 都被顶起来了?:sad: tukiz01

TOP

在我看来这样的非正常方式的请求应该是返回nothing.

TOP

非正常方式的请求应该是返回nothing.


不懂。

$this->render (); 不是返回空值。

TOP

返回列表