3.3. 用AJAX取代传统的form提交

3.3.1. 启用Prototype的JavaScript框架
3.3.2. 改造CGI(controller)
3.3.3. 页面模板充分利用DOM 和JavaScript
3.3.4. 改造示例一:用Ajax.Updater直接进行区域更新
3.3.5. 改造示例二:用Ajax.Request获取并处理数据
  1. 为什么用AJAX?

    使用AJAX,用户对Web的体验会更“敏捷”:数据提交页面不会闪屏;页面局部更新速度快;网络带宽占用低。

  2. AJAX开发相较传统模式的简单之处:

    传统模式下,表单提交则整个页面重绘,为了维持页面用户对表单的状态改变,要多些不少代码。 要在控制器和模板之间传递更多参数以保持页面状态。而AJAX不然,因为页面只是局部更新, 不关心也不会影响页面其他部分的内容。

  3. AJAX开发相较传统模式的难度:

    需要了解、精通JavaScript,而JavaScript存在调试麻烦、浏览器兼容性等很多障碍。

3.3.1. 启用Prototype的JavaScript框架

Prototype是一个JavaScript框架,可以更加容易的使用AJAX实现动态Web。 Pylons内置了prototype脚本。如果想要启用Pylons自带prototype 的JavaScript框架,只要在模板中嵌入如下WebHelpers语句:

<html>
  <head>
    ${h.javascript_include_tag(builtins=True)}

实际上会在页面中产生下面两个JavaScrip包含语句:

    <script src="/javascripts/prototype.js" type="text/javascript"></script> 
    <script src="/javascripts/scriptaculous.js" type="text/javascript"></script>

3.3.2. 改造CGI(controller)

改造之后的CGI(controller的action)不再返回整个页面, 而是返回局部的需要动态更新的内容,或者是返回一段数据供页面中的 JavaScript解析使用。

需要把原来返回一个整个页面的CGI(一个controller的一个方法)改造成多个CGI (多个方法)以针对不同情况返回不同的动态内容。

例如:pySvnManager的check控制器的submit方法实际上要处理两种情况: 一个是当选定一个版本库时要更新页面中的路径列表项(因为不同的版本库定义了不同的授权路径), 另外一个是按下“检查权限”按钮要进行的表单提交,显示用户授权信息。 将check控制器的submit方法改造为AJAX实现,就需要一分为二。

3.3.3. 页面模板充分利用DOM 和JavaScript

页面要动态更新的内容封装在一个DOM容器中;

页面提交修改为执行一个JavaScript函数,该函数调用Ajax.Updater或者Ajax.Request函数;

3.3.4. 改造示例一:用Ajax.Updater直接进行区域更新

当点击权限检查(④)按钮,原来的实现是直接进行表单的提交, 修改之后为执行一段JavaScript代码。

文件 check/index.mako 中用WebHelpers.rails的form_remote_tag 快速创建了一个Ajax Form。

## AJAX Form
<%
  context.write( 
      h.form_remote_tag(
          html={'id':'main_form'}, 
          url=h.url(action='access_map'), 
          update=dict(success="acl_msg", failure="acl_error"), 
          method='post', before='showNoticesPopup()',
          complete='hideNoticesPopup();'+h.visual_effect("Highlight", "acl_msg", duration=1),
      )
  )
%>

出现在页面中,则是如下的代码:

<form action="/check/access_map" id="main_form" method="POST" onsubmit="showNoticesPopup(); 
      new Ajax.Updater({success:'acl_msg', failure:'acl_error'}, '/check/access_map', 
        {asynchronous:true, evalScripts:true, method:'post', onComplete:function(request) 
          {hideNoticesPopup(); new Effect.Highlight(&quot;acl_msg&quot;,{duration:1}); }, 
          parameters:Form.serialize(this)}); 
      return false;">

说明

  • 当Form提交会执行onSubmit部分的代码,而不去执行Form action,因为onSubmit返回false;

  • Ajax.Updater的参数success,是成功执行后用返回信息填充的DOM容器;failure则相反;

  • '/check/access_map'是Ajax要执行的服务器CGI,其返回结果将用于填充相应的DOM容器;

  • onComplete是成功执行Ajax.Updater代码后要执行的JavaScript代码;

  • showNoticesPopup():弹出窗口,提示用户Ajax正在执行过程中,避免用户重复点击;

  • hideNoticesPopup():在Ajax执行完毕,关闭Ajax正在运行的提示窗口;

  • Effect.Highlight()是 scriptaculous.js提供的特效,闪烁更新的区域以引起注意;

  • parameters是用于传递参数,这里把整个表单的数据提交;

3.3.5. 改造示例二:用Ajax.Request获取并处理数据

当从版本库下拉框(②)选择时,将触发更新授权路径的列表(③)。 原来的实现是提交整个表单并刷新整个页面,用AJAX改造后, 只更新授权路径的列表(③)部分。

虽然也可以用Ajax.Updater来更新整个授权路径列表,但为了演示另外一种Ajax处理方式, 以及获得更少的带宽占用和更快的响应,使用Ajax.Request来实现。

版本库下拉框(②)更新时,执行JavaScript函数:update_path(),而非提交表单:

<input type="radio" name="reposinput" value="select" Checked onClick="update_path(this.form)">

函数update_path(),执行Ajax.Request,从"get_auth_path"这个action获取信息, 并用返回值(request.reponseText)为参数调用JavaScript函数ajax_update_path。

function update_path(form)
{
    var repos = "";
    if (form.reposinput[0].checked) {
        repos = form.reposselector.options[form.reposselector.selectedIndex].value;
    } else {
        repos = form.reposname.value;
    }
    var params = {repos:repos};
    showNoticesPopup();
    new Ajax.Request(
        '${h.url_for(controller="check", action="get_auth_path")}', 
        {asynchronous:true, evalScripts:true, method:'post',
            onComplete:
                function(request) 
                { hideNoticesPopup();
                  ajax_update_path(request.responseText);},
            parameters:params
        });
}

函数ajax_update_path(),解析参数code,更新授权路径的下拉列表框。 本例非常简单,直接将参数(code)当作JavaScript代码并执行(eval函数), 这是因为Ajax.Request获取到的内容是字符串格式的JavaScript代码。 最终这些JavaScript代码在函数ajax_update_path中被执行, 并用相应的数据更新了授权路径的列表(③)。

function ajax_update_path(code)
{
    var id = new Array();
    var name = new Array();
    var total = 0;
    
    pathselector = document.forms[0].pathselector;
    lastselect = pathselector.value;
    pathselector.options.length = 0;
    
    try {
        eval(code);
        for (var i=0; i < total; i++)
            {
                pathselector.options[i] = new Option(name[i], id[i]);
                if (id[i]==lastselect)
                pathselector.options[i].selected = true;
            }
    }
    catch(exception) {
      alert(exception);
    }
}