从Perl中使用Ajax

这一篇Using Ajax from Perl是2006年由Dominic Mitchell所撰写,ㄚ琪试着把它翻成中文让各位看看!

[adsense][/adsense]

即使你间接地跟网站开发有点关联,你不可能没有听到再过去的一年Ajax的一些问题,这可能听起来像最热门的词汇而且之后会深陷在详细研究中,尽管它无疑地是时髦的词汇,但它也是相当有用的。

Ajax代表 “非同步的JavaScript和 XML”,它是Jesse James Garret在”Ajax: A New Approach to Web Applications“这篇文章里所创造出来的名词,请忽略这个足球队,他们是冒充的。 😉

那真正的意义是什么?总之,它是如何使您的网页更具互动性,Ajax的核心技术是让你更新你的部份网页却不用从头开始载入所有的网页,这启用了一些很酷的效果,很多人会举出Google MapsGmail 这个你有在用的伟大的例子,但是我喜欢的Ajax的部份是Flickr的那一部份。

当你登入到Flickr的时候,你可以看到你刚上传的相片,在这个例子中,这张照片是我外出骑脚踏车时拿我的相机快拍的,不幸地,手机给了预设的名称,而这样是相当不详细的(图一)。

图一 原来Flickr中的相片

假如你点击标题,它会变成亮度反白的文字输入栏位(图二)。

图二 编辑文字标题

把它变得有点意义一下,按Save按钮,在变回原来的格市前,荧幕会告诉你正在做什么(图三)。

在荧幕的背后,Flickr使用JavaScript来对伺服器产生一个独立的回呼并更新资料,它这样做是通过一种叫做XMLHttpRequest来做的,在你探索Ajax的时候你会听到很多关于XMLHttpRequest的事。

Flickr不必到另一个编辑页面就可以让你编辑相片的很多资料,这是很简单的加强,却可以让整个应用程式更容易使用,这是值得让大家认识这类的各式各样技巧,以丰富您自己的应用程式,另外值得提出的是他们都是成市的加强,假如你把JavaScript停用,Flickr还是会让你编辑这个相片的资料,你只是必须使用一个单独的编辑荧幕来跟伺服器往返地沟通,使用Ajax让你简化你的用户使用经验。

那么所有这混乱的JavaScript又如何?难道几年前我们就摆脱了吗?嗯,是的,这种看起来像Perl Golf的JavaScript早已甩掉包袱走了,我敢肯定这就跟你在发现Perl 5的模组如何运作前,你会对几年前你所写的Perl 4指令码有相同的感觉,现代的JavaScript是不同的猛兽,它相当地标准,所以过去跨浏览器的问题已经不是没有就是变少了,另外也有一个重点使JavaScript尽可能地不突兀,所有这一切的变化,甚至有尝试要将浏览器中的JavaScript重新命名为DOM指令码

Ajax有几个部份:

  • 非同步:要确保在它发生的时候任何活动不会锁住浏览器。
  • JavaScript:处理web浏览器内的网页。
  • XML:从伺服器回传资料。
    Ajax 跟 Perl: CGI::Ajax

    你可能花很多时间找出所有部分在客户端的JavaScript和在伺服端的Perl,以解决如何在程式码中使用Ajax,然而,这是Perl;我们喜欢有点懒惰,幸亏,已经在CPAN上有了一个模组把痛苦给带走:CGI::Ajax

    CGI::Ajax 对你的CGI程式提供了一些基础结构,你告诉它你的一些函式,它就设定好JavaScript然后呼叫它们并且传回结果到你的页面,你不需要担心写JavaScript程式码来做这件事,因为CGI::Ajax会照顾好这件事,你所需要做的是新增一些JavaScript呼叫给你指令码中定义的函式并且让CGI::Ajax处理这些管路。

    CGI::Ajax在JavaScript所建构的函式或多或少遵循着相同的模式,他们需要两个参数:a list of HTML ID列表来取得输入的表单,以及HTML ID的第二个列表来新增插入的结果,在你的HTML中有ID属性是启用这个行为的先决条件,CGI::Ajax处理输入值来查询你的网页,然后新增从伺服端传回来的结果。

    透过你CGI指令码内的函式制作来提供给浏览器,你有能力来做你通常不会作的事,举个例来说,你查询资料库一些值或是查询系统平均负载,任何你可以在Perl做的但却不能在JavaScript里做的现在都变成可能。

    检查使用者名称

    要探索CGI::Ajax,想一想典型的问题,你有一个注册的网页程式,你必须输入使用者名称跟密码来注册这个应用程式,因为这是一个很受欢迎的程式,你要的使用名称可能已被使用,不幸的是,你必须重新执行整个表单然后在告诉你不能有这个使用者名称fluffykitten之前来等候伺服器来收到它,那CGI::Ajax如何来帮助你解这个问题呢?

    开始做一个基本的CGI指令码来处理注册,我会用最小的程式以尽力专注在手头上的这个问题,一旦你可以执行这程式你可以随意地多加一些功能,我会详细解释下面整个指令码,但你也可以下载这个注册的指令码

    开始的时候它像所有好的Perl程式码,但是使用strictwarnings 模组,紧接着是唯一要用的CGI.pm 模组,它会建立一个新的CGI物件然后呼叫 main()来工作。

    #!/usr/local/bin/perl
    # User registration script.
    use strict;
    use warnings;
    
    use CGI;
    my $cgi  = CGI->new();
    main();

    大部分的main()处理HTML的部份,对于实际的指令码,使用像是HTML::TemplateTemplate Toolkit来取代直接在指令码中使用HTML。

    最有趣的事发生在中间这段,首先它检查传来的user参数,如果是这样,程式码检查是否user参数是好的,然后纪录username在我们的资料库中,假如有任何问题,它也会提到那些,最后,它会送出建好的HTML到浏览器来显示。

    sub main {
        my $html = <<HTML;
    <html><head>
    <title>Ordinary CGI demo</title>
    </head><body>
    <h1>Signup!</h1>
    HTML
        if ( my $user = $cgi->param('user') ) {
            my $err = check_username( $user );
            if ( $err ) {
                $html .= "<p class='problem'>$err</p>";
            } else {
                save_username( $user );
                $html .= "<p>Account <em>$user</em> created!</p>\n";
            }
        }
        my $url = $cgi->url(-relative => 1);
        $html .= <<HTML;
    <form action="$url" method="post">
    <p>Please fill in the details to create a new Account.</p>
    <p>Username: <input type="text" name="user" id="user"/></p>
    <p>Password: <input type="password" name="pass" id="pass"/></p>
    <p><input type="submit" name="submit" value="SIGNUP"/></p>
    </form></body></html>
    HTML
        print $cgi->header();
        print $html;
    }

    为了简单地直行一个使用者资料库,我决定储存使用者名单在一个文字档中,每一行一个名字,要检查使用者名称是否已被使用,程式码读取档案然后将每一行与传入的值做比较,不分大小写,假如有任何问题,他们会传回一个字串,假如档案不存在,那么程式码允许任何使用者名称(这帮助来避免你第一次执行这个指令码),再一次,实际的程式可能会使用DBI来储存使用者资料在资料库中。

    sub check_username {
        my ( $user ) = @_;
        return unless -f '/tmp/users.txt';
        open my $fh, '<', '/tmp/users.txt'
          or return "open(/tmp/users.txt): $!";
        while (<$fh>) {
            chomp;
            return "Username taken!" if lc $_ eq lc $user;
        }
        return;
    }

    最后,为了储存一个用户名,程式码必须把它加到档案的最后一行。

    sub save_username {
        my ( $user ) = @_;
        open my $fh, '>>', '/tmp/users.txt'
          or die "open(>>/tmp/users.txt): $!";
        print $fh "$user\n";
        close $fh;
        return;
    }

    现在你应该有一个指令码让你输入使用者名称并且纪录到一个档案中,假如你试着输入相同的名字两次,它会阻止你,就像Hotmail一样,现在想像一下,就像Hotmail一样,你也必须花些时间来做一个captcha影像,那么当你最后管理那些很奇怪的波浪线表示时,你只要按下Submit按钮就会被告知你输入的使用者名称没有影响,然后,你要考虑的另一个用户名,并尝试破解很奇怪的另一行,你会知道尽快告诉使用者使用者名称不能用是很重要的。

    输入 CGI::Ajax

    现在做一些小小的变动,你可以使用Ajax的指令码来检查使用者名称是否有效,那个方法就是任何问题可以马上显示出来,这没有很大的变动,在指令码前头,载入CGI::Ajax模组然后建构一个新的物件,同时注册函式check_username()可以透过Ajax来呼叫,之后直接取代呼叫main(),呼叫build_html(),传入main()的参考,这是CGI::Ajax如何运做的一个重要部份,它给予CGI::Ajax能力来拦截它需要的正常控制流,你也可以下载有Ajax的登录程式码

    #!/usr/local/bin/perl
    # User registration script.
    
    use strict;
    use warnings;
    
    use CGI;
    use CGI::Ajax;
    
    my $cgi  = CGI->new();
    my $ajax = CGI::Ajax->new( check_username => \&check_username );
    print $ajax->build_html( $cgi, \&main );

    在main()函式唯一结构的改变,取代了印出标头跟产生的HTML,现在它传回内容。

    sub main {

    # …

    # print $cgi->header();

    # print $html;

    return $html;

    }

    在那之后,CGI::Ajax会送出内容到你的浏览器。

    在处理Ajax的伺服端之后,现在回来看看客户端,客户端需要CGI::Ajax提供的良好功能,要这样做,你需要一些JavaScript,假如你看了第二只指令码,你会看到CGI::Ajax已经插入某些在<head>这个区块内的JavaScript,这是你公开Perl函式给JavaScript的程式码,所有的事件就是连接发生在那些公开的Perl函式的事件。

    假如你之前有用过JavaScript,你可能要想像onchange的属性,那是正确的想法(在使用者名称栏位变动时触发Ajax呼叫),但是做这事的理想方法,因为它会干扰,还有实在没有必要要在HTML中有JavaScript,另一个作法可以建立一个小档案来系结标记语言到公开的Perl函式(下载binding.js )。

    开始的时候它启动Simon Willison写的addLoadEvent函式,这个函式在页面完成载入时会使用一段JavaScript并且执行,有用的是你可以不只一次地呼叫它也不会有不良的影响,你可以使用window.onload来直接处理,但是这得移除先前使用它的程式码,如果到处使用addLoadEvent(),这会是一个问题。

    // Run code when the page loads.

    function addLoadEvent(func) {

    var oldonload = window.onload;

    if (typeof window.onload != ‘function’) {

    window.onload = func;

    } else {

    window.onload = function() {

    oldonload();

    func();

    }

    }

    }

    接下来的JavaScript做了实际的工作,虽然在做任何事之前,含有一个小小地检查来确认浏览器是可以处理所有这些的Ajax,藉着没有含括号的浏览器函式的名称(document.getElementById),JavaScript会传回一个参考,假如这个函式不存在,它会传回null,假如这个函是不存在,这个程式码只会直接传回,导致这一个页面没有Ajax功能,这是优美的退化,如果加强的行为不能运作它会让普通的行为发生。

    当程式码知道它的执行安全时,它会针对我们输入的username元素来查询文件,假如有找到,程式码只要每次username栏位有改变就会安排呼叫check_username()函式,在文件里有两个参数是ID的列表,第一个参数它的值会传到伺服器的check_username(),第二个包含ID的参数会从函式那里新增传回的值。

    // Set up functions to run when events occur.
    function installHandlers() {
      if (!document.getElementById) return;
      var user = document.getElementById('user');
      if (user) {
          // When the user leaves this element, call the server.
          user.onchange = function() {
              check_username(['user'], ['baduser']);
          }
      }
    }

    installHandlers()定义下,所有要保留的就是确保页面载入时它会真正执行。

    addLoadEvent( installHandlers );

    binding.js 完成的时候,你需要将两个小改变弄到产生的HTML中,首先,含括一个指令码script标签来真正载入它:

    <script type="text/javascript" src="binding.js"></script>

    第二,建立一个id为baduser的元素来新增结果,我建立一个空白的强调标签在username栏位后面。<p>Username: <input type="text" name="user" id="user"/>

    <em id="baduser"></em></p>

    在那个地方,你应该能够注册一个使用者名称,然后当你试着注册两次的时候看到它失败,看看username无效的时候这个标签如何提醒。

    Inside CGI::Ajax

    现在你知道如何使用CGI::Ajax来动态更新你的网页, 究竟里面有什么变化让它可以这样运作?你已经看到了check_username()的JavaScript版本来收集input栏位的值并将值传给你原来的CGI指令码,你可以看见当你在建构CGI::Ajax物件后多加一行到指令码时所发生的情形。

    $ajax->JSDEBUG(1);

    在那个地方,CGI::Ajax会纪录每个到伺服器的呼叫在你的网页下面。在我的伺服器上,它看起来像这样:

    http://localhost/~dom/cgi-ajax/ajax.cgi?fname=check_username&args=dom&user=dom

    假如你按这个连结,你会看到它传回你所用的字串Username 'dom',不会有别的资料,它几乎忽略了程式的大半部份而只送出check_username()的结果,当程式的主要部份呼叫ajax->build_html(),CGI::Ajax检查一个叫做fname的参数存在,假如有找到,那么它会检查看看函式是否有注册,假如有就呼叫,传args的参数,然后传回一个单一的函式回去给浏览器,完全避开主程式。

    真实世界的CGI::Ajax

    Ajax是一个工具,就像很多其他的人已经在Web上设计程式那样可用,它有一些比较适合或不适合使用的地方,就像HTML中的table元素一样,我选择username验证作为一个范例是因为我认为它是Ajax可以真正用来增加一个功能到现有的Web应用程式中的一个很好的例子,使用Ajax来增强表单,特别是很常且复杂的那种,能够创造奇迹的使用,当然也有其它的地方,就像我在开始的时候指出Flickr,行内编辑是一个伟大的福音。

    像所有的工具那样,知道什么时候不用跟什么时候可用一样重要,像CGI::Ajax的套件使它非常便于使用Ajax,所以你必须保持克制,加入少许的Ajax就可以让它更适合地重新载入整个页面真是太简单了,假如你发现你正使用Ajax呼叫来更新网页的大部分,那么它很可能不值得你麻烦Ajax了,真的,最好的方针是做一些可用性测试,想想你的使用者如何与你的应用程式互动,什么是最好的方式可以让你帮助他们实现他们正在尝试做的?

    有一些很好的资源可以用来决定什么时候可用什么时候不用Ajax,最完整的似乎是Ajax Patterns,有些时候 可以看看欧莱礼的书,有一篇来自Alex Bosworth的部落格文章提供了他的意见在Ten Places You Must Use Ajax里 (即使真的只有6点而已), Alex也有一个好的作品在”Ajax Mistakes”,也值得去注意看看。

    也有一些技术上的原因来考虑什么时候在你的应用程式里执行Ajax,你必须意识到任何使用者都可以存取你正曝露的伺服端函式,即使你可能会认为他们只是内部的程式而已,你的应用程式突然有一个API,基于安全或效能的缘故,你可能想要重新考虑你所曝露的(虽然这个建议也同样适用于你应用程式中的一般网页),还要注意你从CGI::Ajax取得的API跟你的应用程式内部的紧密结合,假如你改变了一个曝露的函式名称,你必须改变API,假如API是一个很大的正在进行中专案的一部分,你可能想要考虑花些时间来看看更多的REST-之类的介面来替代,这些往往容易跟其他语言的程式设计师来工作。

    现在你的站台真的有了API,你可能想要考虑文件化来说明它如何运作,所以你的使用者可以用这个API来建构,这就是Flickr已经做过的伟大影响,那些使用Flickr的人们从没有想过有新的工具,只因为他们让他们的使用者可以存取API。

    不要让这一切阻止你;你已经看过了要添加一些闪光到你的应用程式中是如何地简单,想想如何让用户的生活更简单吧。