喜悦国际村 » PHP高级编程 » 用 PHPRPC 实现 Ajax 级联下拉菜单

页: [1]
andot2006-6-7 02:45 AM
用 PHPRPC 实现 Ajax 级联下拉菜单

[b]该文首发于:[url=http://www.coolcode.cn/?p=181]CoolCode.CN[/url][/b]

级联下拉菜单就是从一个下拉菜单中选中一项后,相应的另一个下拉菜单的内容会随之改变。

一般来说,最简单的,就是每次选中都提交一次表单,刷新整个页面。这也是用户体验度最差的。

另一种是把所有选项在第一次加载时就全部载入整个页面中的 JavaScript 数组中,然后级联通过 JavaScript 来控制,在整个数据量不大时,这是一个不错的实现无刷新并且快速的方法,但是当整个数据量非常大时,这种方法就会使第一次加载变得非常慢了。

还有就是采用 Ajax 方式,即开始只载入第一层菜单的内容,当用户选中第一层菜单的某项时,再通过 XmlHttpRequest 来获取相应选项所对应的第二层菜单的内容。这种方式效果最好,但是采用传统方式来编写这样的 Ajax 程序代码量会比较多。而且如果设计不好,服务器端返回菜单内容的程序的可复用性也会很差。

但是在本文中你会看到用 PHPRPC 来实现这种 Ajax 效果是多么的简单,并且还会具有非常高的可复用性。

本文以省市两级级联下拉菜单为例,为了举例方便,本文中采用的是 SQLite 数据库,因为这个文件型数据库比较容易部署,而且查询效率很高(当然创建该数据库的效率不高,但创建仅一次而已,该数据库在该程序中内容是不变的),不过服务器需要安装 SQLite 扩展。

这个数据库中的表只有 2 个,一个 province 表,一个 city 表。province 表中,只有 id 和 name 两个字段,分别是省份编号(主键)和省名。city 表中,有 id、name 和 pid 三个字段,id 是城市编号,name 是城市名,pid 是城市所在省的编号,与 province 表中的省份编号相对应。

创建该数据库的程序这里就不给出来了,它包含在后面提供的实例下载中。

下面来看看创建这个程序的服务器端有多么简单,为了提高可复用性,我们把服务器端分为了 2 个文件,一个是 function.php,另一个是 rpc.php。function.php 中定义了实际的远程调用函数,但是他们也可以作为服务器端的本地函数调用,你会发现他们跟服务器端的普通函数没有任何区别:

[php]
<?php
$sqlite = new SQLiteDatabase('area.db');

function get_province() {
    global $sqlite;
    $sql = "select * from province order by id";
    return $sqlite->arrayQuery($sql, SQLITE_ASSOC);
}

function get_city($pid) {
    global $sqlite;
    $pid = sqlite_escape_string($pid);
    $sql = "select * from city where pid = $pid order by id";
    return $sqlite->arrayQuery($sql, SQLITE_ASSOC);
}
?>
[/php]

而 rpc.php 更加简单,它是提供给客户端调用的接口,它只有 3 行语句:

[php]
<?php
require_once('function.php');
require_once('phprpc_server.php');
new phprpc_server(array('get_province', 'get_city'));
?>
[/php]

其中最后一句,就是指定哪些函数要暴露给客户端。只有指定的函数客户端才可以调用,这样可以保证服务器的安全性。

服务器端到此就创建完了。你会发现服务器端只负责把数据查询出来返回给客户端就完事了,其它的不做任何处理。

那么下面该看一看客户端了,客户端虽然很简单,但是我还是把它分成了两个文件,一个 js 文件,一个 html 文件,你会发现用 PHPRPC,客户端都不需要用 PHP。

[CODE]
// 创建 phprpc 客户端对象 rpc
phprpc_client.create('rpc');

var city = []; // 用于缓存已加载的城市数据

/*
* 清除 select 中的选项,该方法可复用
*
* so: 要清除选项的 select 对象
*
*/
function clear_select(so) {
    for (var i = so.options.length - 1; i > -1; i--) {
        // 有些浏览器不支持 options 属性的 remove 方法,
        // 但支持 DOM 的 removeChild 方法(比如:Konqueror)
        if (so.options.remove) {
            so.options.remove(i);
        }
        else {
            so.removeChild(so.options[i]);
        }
    }
}

/*
* 设置 select 中的选项,该方法可复用
*
* so: 要设置选项的 select 对象
*  d: 选项数据数组
* vf: 选项值所对应的数组中的字段名
* tf: 选项文本所对应的数组中的字段名
*/
function set_select(so, d, vf, tf) {
    for (var i = 0, n = d.length; i < n; i++) {
        var opt = document.createElement('option');
        opt.text = d[i][tf];
        opt.value = d[i][vf];
        // 有些浏览器不支持 options 属性的 add 方法,
        // 但支持 DOM 的 appendChild 方法(比如:Konqueror)
        if (so.options.add) {
            so.options.add(opt);
        }
        else {
            so.appendChild(opt);
        }
    }
}

// 设置省份的下拉菜单
function set_province_select(d) {
    var so = document.getElementById('province');
    set_select(so, d, 'id', 'name');
    // 设置首选省份的城市下拉列表
    change_province(1);
}

// 设置城市的下拉菜单
function set_city_select(d, vf, tf) {
    var so = document.getElementById('city');
    // 清空原有选项
    clear_select(so);
    // 设置新选项
    set_select(so, d, vf, tf);
}

// 当省份改变,相应的改变城市列表
function change_province(pid) {
    // 如果已缓存,则直接显示缓存中的列表
    if (city[pid]) {
        set_city_select(city[pid], 'id', 'name');
    }
    else {
        // 否则,先显示载入中
        set_city_select([['', 'Loading...']], 0, 1);
        // 然后调用远程过程载入城市信息
        // 调用远程过程时,最后一个参数指定的是回调函数
        rpc.get_city(pid, function (result) {
            // 把载入的数据放入缓存
            city[pid] = result;
            // 更新城市列表
            set_city_select(result, 'id', 'name');
        });
    }
}

// 定义当 rpc 客户端初始化(use_service)完毕后执行的内容
rpc.onready = function () {
    // 调用获取省份内容的远程过程,并设置回调函数为 set_province_select
    rpc.get_province(set_province_select);
}
[/CODE]

[CODE]
<html>
<head>
<script type="text/javascript" src="phprpc_client.js"></script>
<script type="text/javascript" src="area.js"></script>
</head>
<body onload="rpc.use_service('rpc.php');">
<select id="province" onchange="change_province(this.value);"></select>
<select id="city"></select>
</body>
</html>
[/CODE]

上面的 html 中包含的 phprpc_client.js 是压缩版本(因为不需要用到加密,这里是 lite 压缩版)的,这样可以免去包含多个 js 文件的麻烦。

大家会发现这个程序不但可复用性好(比如 clear_select 和 set_select 两个函数也可以在其它程序中使用),而且使得整个程序的思路清晰,比如那个缓存功能,在这里实现的就非常的简单,而且效果也非常的好。

[list]
[*] [URL=http://test.coolcode.cn/selectmenu]演示程序[/URL]
[*] [URL=http://test.coolcode.cn/selectmenu/selectmenu.zip]实例下载[/URL]
[/list]

通过 PHPRPC,你不需要再专注于服务器端和客户端的数据格式交换,不需要再去考虑 XmlHttpRequest 对象的创建和使用,PHPRPC 会自动帮你完成这一切,你只需要关注具体的事务就可以了。用 PHPRPC 来做 Ajax 编程,就是这么简单。

瞎逛悠2006-6-7 03:20 AM
萝卜青菜,各有所爱

good82006-6-7 11:53 AM
哈,不错!

一草一木2006-6-9 06:35 AM
版主呢。。。应该加精才对。。。。
准备在项目中使用PHPRPC了。。。。。

只爱一个人2006-6-9 09:03 AM
我看了半天也没有看到PHPRPC的独特之处
不知道哪位高人能说说不

andot2006-6-10 07:06 AM
PHPRPC 与 JSON-RPC、XML-RPC 的比较

先列个表:

[code]
特性                          PHPRPC              JSON-RPC         XML-RPC
序列化方式                     PHP                  JSON             XML
序列化正负无穷大               支持                不支持          不支持
非零基数组传递                 支持                不支持          不支持
非连续下标数组传递             支持                不支持          不支持
关联下标数组传递               支持                不支持          不支持
递归结构数组传递               支持                不支持          不支持
递归结构对象传递               支持                不支持          不支持
对象所属类传递                 支持                不支持          不支持
可变类型参数传递               支持                 支持           不支持
变长参数传递                   支持                 支持           不支持
引用参数传递                   支持                不支持          不支持
服务器端输出重定向             支持                不支持          不支持
服务器端错误重定向             支持                不支持          不支持
浏览器跨域调用支持             支持                不支持          不支持
网络负载                        低                   低              高
加密传输                       内置                  无              无
易用性                          好                  一般             差
[/code]


然后,再慢慢道来:

[b]序列化方式[/b]

首先我们来看看序列化方式,PHPRPC 采用的是 PHP 方式,JSON-RPC 用的是 JSON 方式,XML-RPC 是用的 XML 方式。这三种序列化方式中 PHP 序列化方式的功能最强大,而且序列化出来的结果在保持纯文本的状态下,还能保证最小负载。

PHP 序列化方式和 JSON 的比较在 [url=http://www.coolcode.cn/?p=177]JSON-RPC vs PHPRPC —— 序列化数据[/url]一文中已经作了比较详细的阐述,而 XML-RPC 的 XML 序列化格式所能表示的内容跟 JSON 相当,他们都无法序列化正负无穷大、非零基数组、非连续下标数组、关联下标数组、递归结构数组、递归结构对象等数据类型,而且序列化的对象相当于一个关联下标数组(或者说是结构体),因为它没有类名。

XML 序列化方式还有个问题,就是它序列化后的数据将变得很大,因此它会加重网络负载。

[b]参数传递[/b]

PHP、JavaScript 等许多脚本语言都是弱类型语言,在这些语言当中,一个函数的参数可以是任意类型,而返回的结果也可以是任意类型的。许多语言还提供变长参数支持,即同一个函数的参数个数是不固定的,参数个数可以是任意多个。而且大多数高级语言都支持引用参数传递。这些特征给开发者提供了很大的灵活性。

而 JSON-RPC 不支持引用参数传递,XML-RPC 更差,这三种参数传递的特性都不支持。这些限制使的原本具有灵活性的语言只能提供不具有灵活性的远程调用。但 PHPRPC 对于这三种参数传递的特性都支持,可以给开发者提供最大的灵活性。

[b]重定向[/b]

对于本地过程调用,输出和返回结果显然是不同的,PHPRPC 提供了服务器端输出重定向到客户端的功能,它会将服务器端的输出以区别于远程过程调用结果的另一种方式单独返回。并且它还可以将出错信息和错误代码也以另一种方式单独返回给客户端。这就大大的方便了调试和应用。而 XML-RPC 和 JSON-RPC 都不能提供这种支持。

[b]跨域调用和浏览器支持[/b]

对于 ajax 编程,最令人头疼的一件事,就是无法跨域调用,这是由 XmlHttpRequest 的安全模型所限制的。JSON-RPC 和 XML-RPC 都是完全使用的 XmlHttpRequest 对象来完成浏览器到服务器通讯的,因此,它们都无法做到跨域调用。

而 PHPRPC 则不完全是使用 XmlHttpRequest 对象与服务器通讯,只有当服务器处于本地域,并且浏览器也支持 XmlHttpRequest(或 XmlHttp)对象时,PHPRPC 客户端才会使用 XmlHttpRequest 对象与服务器通讯,而当服务器处于非本地域或者浏览器不支持 XmlHttpRequest(和 XmlHttp)对象时,PHPRPC 客户端会采用另一种方式——脚本附加技术(或者叫做脚本替换技术)来实现浏览器与服务器的通讯,但是这一切都是由 PHPRPC 客户端对象自动完成的,你无须在为此操心。这正是采用了这种技术,PHPRPC 所能支持的浏览器比 JSON-RPC 和 XML-RPC 更广泛,PHPRPC 不但可以支持 PC、Mac 等机器上的浏览器,而且支持手持设备上(PPC 或者 SmartPhone)的 IE、Opera Mobile、Opera Mini 等浏览器,而这些都是 JSON-RPC 和 XML-RPC 所无法做到的。

[b]加密传输[/b]

对于 ajax 编程,还有一件令人头疼的事情就是数据传输的安全性,因为这时候客户端与服务器端的通讯已经是纯数据通讯了,这些信息很容易就会被别有用心的人截获,因此关键的数据在这种情况下传输就比较危险了。通常唯一能够采取的措施恐怕就是要部署 https 服务器,但是一般没有自己服务器的用户就做不到了。而且有时在整个系统中只有少数的数据是需要保密的(例如用户名、密码),而其他信息的传输,也许并不是那么的敏感,而部署 https 服务器的话,所有的内容也就全部加密了。最后还有个问题,http 和 https 会认为是不同的域(即使是相同的域名),因此,你无法从一个 http 页面上用 XmlHttpRequest 去请求一个 https 上的数据。

而 PHPRPC 却已经内置了加密传输的机制,支持不加密传输、单向加密传输和双向加密传输这 3 中数据传输方式。因此,它使得在 http 上安全传输数据成为了现实,而 JSON-RPC 和 XML-RPC 却完全没有这方面的支持。

[b]易用性[/b]

上面说了 PHPRPC 具有那么多 JSON-RPC 和 XML-RPC 的特性,你也许会觉得 PHPRPC 也许很庞大很难用吧。而事实上,PHPRPC 在易用性方面做的比 JSON-RPC 和 XML-RPC 都好。举个很简单的例子。比如服务器端,要创建一个远程调用,如果是 JSON-RPC,你需要单独的以不同于编写本地函数的方式来编写远程调用函数,而 XML-RPC 不但要单独的以不同于编写本地函数的方式来编写远程调用函数,而且还需要为远程调用函数来指定各个参数的类型和返回值的地类型等而外的工作。而 PHPRPC 则完全不需要做这些,编写一个 PHPRPC 的远程调用和编写一个本地调用没有任何区别(唯一的区别就是参数和返回结果不能是资源类型,因为资源类型无法序列化,但这一点不是问题,因为 JSON-RPC 和 XML-RPC 也做不到),你甚至可以直接把一个 PHP 中的函数(比如 md5())作为远程过程提供给客户端,你需要做的唯一一件事情,就是告诉服务器,你希望客户端能够调用哪些函数。这只是一个例子,在其他方面 PHPRPC 的易用性也远远好于 JSON-RPC 和 XML-RPC,如果你不敢相信的话,读一读 [url=http://www.coolcode.cn/?p=148]PHPRPC 用户指南[/url] 就知道我没有骗你了。

从上面的比较,不用我说,你也应该发现 PHPRPC 远远好于 JSON-RPC 和 XML-RPC 这个事实了吧。不过最后,我再说点 PHPRPC 的不足吧。目前,PHPRPC 唯一的不足,就是它现在只有 PHP 服务器的版本,其他语言(例如 ASP、.NET、Java 等)的服务器版本都还没有实现,不过相信用不了多久它们就会出现了,因为它们已经在开发中了。

henrik2006-6-12 11:26 AM
這個好用
推一個
感謝樓主的教學

mzsh2006-6-12 01:05 PM
你的 PHPRPC 好像是吧PHP函数弄到客户端来调用,而JSON是吧数据转换格式传到客户端/服务器端,你那PHPRPC和JSON比较,好像不是一个类型的东西啊,也许是我认识的太肤浅~

andot2006-6-13 11:25 AM
你如果仔细看会发现,我比较的不是 PHPRPC 和 JSON,而是 PHPRPC 和 JSON-RPC。另外,我对  PHP 序列化和 JSON 的也作过比较,上面有连接。这两个是一个级别的东西。所以不是我写错了,可能是你没有看清楚吧。

e_yangrz2006-6-14 02:18 AM
学习ing:lol

andot2006-6-14 10:14 AM
今天,PHPRPC 新版本 20060614 发布了!该版本增加了 ASP 版本的 PHPRPC 服务器和客户端的实现和最新的手册。其它地方也作了一些改进。相对于前几次更新,这次更新还是比较大的。

zmingcom2006-6-23 10:50 AM
谢谢楼主``学习中`


查看完整版本: 用 PHPRPC 实现 Ajax 级联下拉菜单


Powered by Discuz! Archiver 6.1.0  © 2001-2006 Comsenz Inc.
Processed in 0.007143 second(s), 2 queries