让我们编写一个基于 jQuery 的 Drupal 模块。我们将建立一个如图17-2所示的 Ajax 的投票小组件,它可以让用户为喜欢的文章添加一分。我们使用 jQuery 来处理投票和总分的改变,而不用重新加载整个页面。我们还添加一个基于角色的授权,这样只有具有”rate 内容”授权的用户才允许投票。由于每个用户的每次投票只能增加一份,让我们将模块的名称命名为”plus1″。

图17-2 投票组件

在我们接触到 plus1 的 jQuery 部分以前,首先我们需要构建模块所需的基本一些代码。如果你以前从来没有创建过模块,请参看第2章。如果有经验的话,现在就开始了。

在 sites/all/modules/custom 下面创建一个名为 plus1 的目录(你可能需要创建这个目录如果它不存在的话)。在目录 plus1 下面,创建文件 plus1.info,它包含下面的代码:

name = Plus 1
description = “A +1 voting widget for nodes. ”
version = “$Name$”

该文件将模块注册到 Drupal 中,这样可以通过管理页面启用或者禁用它。

接着,你将创建 plus1.install 文件。这个 PHP 文件里面的函数将在模块启用或者禁用的时候调用,一般用来创建或者删除数据库表。在这里我们想用它来追踪谁为哪个节点投了票。

<?php
// $Id$
/**
* Implementation of hook_install().
*/
function plus1_install() {
switch ($GLOBALS['db_type']) {
case ‘mysql’:
case ‘mysqli’:
db_query(”CREATE TABLE {plus1_vote} (
uid int NOT NULL default ‘0′,
nid int NOT NULL default ‘0′,
vote tinyint NOT NULL default ‘0′,
created int NOT NULL default ‘0′,
PRIMARY KEY (uid,nid),
KEY score (vote),
KEY nid (nid),
KEY uid (uid)
) /*!40100 DEFAULT CHARACTER SET UTF8 */”);
break;
case ‘pgsql’:
db_query(”CREATE TABLE {plus1_vote} (
uid int NOT NULL default ‘0′,
nid int NOT NULL default ‘0′,
vote tinyint NOT NULL default ‘0′,
created int NOT NULL default ‘0′,
PRIMARY KEY (uid,nid)
);”);
db_query(”CREATE INDEX {plus1_vote}_score_idx ON {plus1_vote} (vote);”);
db_query(”CREATE INDEX {plus1_vote}_nid_idx ON {plus1_vote} (nid);”);
db_query(”CREATE INDEX {plus1_vote}_uid_idx ON {plus1_vote} (uid);”);
break;
}
}
/**
* Implementation of hook_uninstall().
*/
function plus1_uninstall() {
db_query(’DROP TABLE {plus1_vote}’);
}

还有就是添加 plus1.css 文件。这个文件不是必需的,但它可以使投票组件看起来更美观,如图17-3所示。

图17-3 带有 CSS 和不带有的对比

向 plus1.css 添加如下内容:

div.plus1-widget {width: 100px;margin-bottom: 5px;text-align: center;}
div.plus1-widget .score {padding: 10px;border: 1px solid #999;background-color: #eee;font-size: 175%;}
div.plus1-widget .vote {padding: 1px 5px;margin-top: 2px;border: 1px solid #666;background-color: #ddd;}

现在你创建了起支撑作用的文件,现在让我们将注意力集中到 jQuery 的 JavaScript 文件,还有模块文件本身。创建两个空文件,其中一个命名为 jquery.plus1.js,另一个命名为 plus1.module,并将它们放到 plus1 文件夹下。在接下来的步骤中,你将逐步的像这两个文件添加代码。总结一下,你创建了以下文件:

sites/all/modules/plus1/
jquery.plus1.js
plus1.css
plus1.info
plus1.install
plus1.module

创建模块

在一个文本编辑器中打开空的 plus1.module 文件,并向其中添加标准的 Drupal 头部文档:

<?php
// $Id$
/**
* @file
* A simple +1 voting widget.
*/

接下来你要一个一个的添加你要用到的 Drupal 钩子函数。其中一个比较简单的是 hook_perm() 的使用,它让你为 Drupal 的基于角色的访问控制页面添加一个”rate 内容”的授权,你将使用这一授权来阻止匿名用户在未创建帐号或者登陆的时候投票。

/**
* Implementation of hook_perm().
*/
function plus1_perm() {
return array(’rate content’);
}

现在你将开始实现一些 Ajax 功能。jQuery 的一个重要特性提交它自己的 HTTP GET 或 POST 请求,这将使你将投票提交给 Drupal 而不用刷新整个页面。jQuery 将拦截到对投票链接的点击,然后向 Drupal 发送一个请求, Drupal 将保存投票并返回分数。jQuery 将使用新的分数来更新页面上的分数。图17-4展示了我们要做的场景图。

图17-4 投票更新流程的概貌图

一旦 jQuery 拦截了对投票链接的点击事件,它需要能够将 URL 传递给 Drupal 来提交投票。我们使用钩子函数 hook_menu 来讲由 jQuery 提交的投票 URL 映射到一个 Drupal 的 PHP 函数。该 PHP 函数将投票保存到数据库中,并返回一个分数给 jQuery(以 JSON 的形式,即 JavaScript Object Notation)。

/**
* Implementation of hook_menu().
*/
function plus1_menu($may_cache) {
$items = array();
if ($may_cache) {
$items[] = array(
‘path’ => ‘plus1/vote’,
‘callback’ => ‘plus1_vote’,
‘type’ => MENU_CALLBACK,
‘access’ => user_access(’rate content’),
);
}
}
return $items;

在前面的函数中,当路径为 plus1/vote 的请求进来以后,如果请求该路径的用户拥有”rate content”授权,那么将有函数 plus1_vote() 处理它。路径 plus1/vote/3 翻译为 PHP 函数就是调用 plus1_vote(3) (参看第4章,关于 Drupal 的菜单/回调系统的更详细信息)。

/**
* Called by jQuery.
* This submits the vote request and returns JSON to be parsed by jQuery.
*/
function plus1_vote($nid) {
global $user;
// Authors may not vote on their own posts.
$is_author = db_result(db_query(’SELECT uid FROM {node} WHERE nid = %d AND uid = %d’, $nid, $user->uid));
// Before processing the vote we check that the user is logged in,
// we have a node ID, and the user is not the author of the node.
if ($user->uid && ($nid > 0) && !$is_author) {
$vote = plus1_get_vote($nid, $user->uid);
if (!$vote) {
$values = array(
‘uid’ => $user->uid,
‘nid’ => $nid,
‘vote’ => 1,
);
plus1_vote_save($values);
watchdog(’plus1′, t(’Vote by @user accepted’, array(’@user’ => $user->name)));
$score = plus1_get_score($nid);
// This print statement will return results to jQuery’s request.
print drupal_to_js(array(
’score’ => $score,
‘voted’ => t(’ You voted ‘)
)
);
}
}
exit();
}

上面的函数 plus1_vote() 保存了当前的投票并向 jQuery 返回信息,信息的格式是包含了新分数和字符串 You voted 的关联数组,该字符串用于替换投票组件下面的”Vote”文本,我们使用了 t(’You voted’) 而不是在 jQuery 中直接创建它,这样就可以将它翻译成其它语言。这个数组传给了 drupal_to_js(),drupal_to_js() 将 PHP 变量转化为 JavaScript 的等价形式,在这里将一个 PHP 关联数组转化为了一个 JavaScript 关联数组。Drupal 将 JavaScript 数组序列化为 JSON 格式(关于 JSON 的更多信息,参看 http://en.wikipedia.org/wiki/JSON)。在上面的代码中我们创建了几个基本函数,现在让我们创建这些函数:

/**
* Return the number of votes for a given node ID/user ID pair.
*
* @param $nid
* A node ID.
* @param $uid
* A user ID.
* @return Integer
* Number of votes the user has cast on this node.
*/
function plus1_get_vote($nid, $uid) {
return (int) db_result(db_query(’SELECT vote FROM {plus1_vote} WHERE nid = %d AND uid = %d’, $nid, $uid));
}
/**
* Return the total score of a node.
*
* @param $nid
* A node ID.
* @return Integer
* The score.
*/
function plus1_get_score($nid) {
return (int) db_result(db_query(’SELECT SUM(vote) FROM {plus1_vote} WHERE nid = %d’, $nid));
}
/**
* Save the vote.
*
* @param $values
* An array of the values to save to the database.
*/
function plus1_vote_save($values) {
db_query(’DELETE FROM {plus1_vote} WHERE uid = %d AND nid = %d’, $values['uid'],$values['nid']);
db_query(’INSERT INTO {plus1_vote} (uid, nid, vote, created) VALUES (%d, %d, %d,%d)’, $values['uid'], $values['nid'], $values['vote'], time());
}

现在,基本的 getter 和 setter 已经写好,现在让我们关注投票小部件生成代码:

/**
* Create voting widget to display on the webpage.
*/
function plus1_jquery_widget($nid) {
// Load the JavaScript and CSS files.
drupal_add_js(drupal_get_path(’module’, ‘plus1′) .’/jquery.plus1.js’);
drupal_add_css(drupal_get_path(’module’, ‘plus1′) .’/plus1.css’);
global $user;
$score = plus1_get_score($nid);
$is_author = db_result(db_query(’SELECT uid FROM {node} WHERE nid = %d AND uid = %d’, $nid, $user->uid));
$voted = plus1_get_vote($nid, $user->uid);
return theme(’plus1_widget’, $nid, $score, $is_author, $voted);
}
/**
* Theme for the voting widget.
*/
function theme_plus1_widget($nid, $score, $is_author, $voted) {
$output = “;
$output .= “;
$output .= $score;
$output .= “;
$output .= “;
if ($is_author) { // User is author; not allowed to vote.
$output .= t(’Votes’);
} elseif ($voted) { // User already voted.
$output .= t(’You voted’);
} else { // User is eligible to vote.
// The class plus1-link is what we will search for in our jQuery later.
$output .= l(t(’Vote’), “plus1/vote/$nid”, array(’class’ => ‘plus1-link’));
}
$output .= “;
$output .= “;
return $output;
}

在前面的方法 plus1_jquery_widget() 中,我们首先加载相应的 CSS 和 Javascript 文件,然后将小部件的主体化委托给了我们自己创建的定制函数 theme_plus1_widget()。记住 theme(’plus1_widget’) 实际上调用的就是 theme_plus1_widget() (参看第8章关于它是如何工作的)。创建一个独立的主题函数,而不是将 HTML 代码放到方法 plus1_jquery_widget() 中,这将允许设计者在它们想改变外观时能够覆写该函数。我们的主题函数 theme_plus1_widget(),为关键的 HTML 元素创建 CSS 类选择器,这使得 jQuery 能够非常方便的访问到它们。还有,让我们看一下链接的 URL,它指向 plus1/vote/$nid,其中 $nid 是当前已发布节点的 ID。当用户点击链接时,jQuery 将代替 Drupal 对它进行拦截并处理。我们是通过使用 jQuery 监控该链接上的 onClick 事件来完成拦截的。看一下在我们创建链接时是如何定义 CSS 类选择器 plus1-link。看一下在我们后面的 Javascript 代码中选择器的使用 a.plus1- link。它由 元素和 CSS 类选择器 plus1-link 组成。

函数 plus1_jquery_widget() 生成发送给浏览器的小部件。你想让它出现在节点的查看页面中,这样当用户查看节点时,就可以对它们投票了。你能猜一下使用哪一个 Drupal 钩子函数实现这一点吗?他就是我们的老朋友 hook_nodeapi(),它允许我们可以像创建节点一样任意的修改节点。

/**
* Implementation of hook_nodeapi().
*/
function plus1_nodeapi(&$node, $op, $teaser, $page) {
switch ($op) {
case ‘view’:
// Show the widget, but only if the full node is being displayed.
if (!$teaser) {
$node->content['plus1_widget'] = array(
‘#value’ => plus1_jquery_widget($node->nid),
‘#weight’ => 100,
);
}
break;
case ‘delete’:
db_query(’DELETE FROM {plus1_vote} WHERE nid = %d’, $node->nid);
break;
}
}

我们将重量(weight)元素的值设为一个大点的值100,这样就确保了小部件出现在节点的底部而不是顶部。我们在删除(delete)情况下,也放置了一段代码,这样当节点被删除时与其相关的投票记录也将一同被删除。

上面就是 plus1.module 的全部内容了,离我们整个模块的完成现在就剩下编写 jquery.plus1.js 了,它不足15行代码:

// $Id$
// Global killswitch: only run if we are in a supported browser.
if (Drupal.jsEnabled) {
$(document).ready(function(){
$(’a.plus1-link’).click(function(){
var voteSaved = function (data) {
var result = Drupal.parseJson(data);
$(’div.score’).fadeIn(’slow’).html(result['score']);
$(’div.vote’).html(result['voted']);
}
$.get(this.href, null, voteSaved);
return false;
});
});
}

你应该将你的 jQuery 代码封装在 Drupal.jsEnabled 测试中,该测试确保了当前浏览器对特定 DOM 方法的支持(如果不支持的话,我们的 Javascript 代码就不被执行。

该 Javascript 代码向 a.plus1 -link 添加了一个事件侦听器(还记不记得我们将 .plus1-link 定义为 CSS 类选择器?),这样当用户点击链接时,它将触发一个 HTTP GET 请求,发送到它指向的 URL。当请求完成后,返回值(从 Drupal 中返回)作为 data 参数传递到匿名函数中,用该函数给变量 voteSaved 赋值。返回值是一个序列化为 JSON 格式的 Javascript 数组,所以你要使用 Drupal.parseJson() 对其反序列化。通过关联数组的键来引用数组,数组键在 Drupal 内部的 plus1_vote() 函数中指定。最后 Javascript 更新了分数并将文本”Vote”改为”You Voted”。为了阻止加载整个页面(因为这是 Ajax 要求的),我们在 Javascript 的 jQuery 函数中将返回值置为 false。

提示:如果你在计算机上自己动手实践,但是小部件却不能正常工作。你需要检查一下,你是不是以内容创建者的身份登录了(这是因为用户不能对自己创建 的内容投票),并检查一下登录用户是否拥有权限”rate content”(”评论内容”)。一个好用的 Ajax 请求测试工具是名为 Firbug 的 FireFox 插件,你可以从 http://getfirebug.com/ 下载到它。

扩展该模块的方式

对本模块的一个很好的扩展,是允许站点管理员对特定节点类型启用投票小部件。你可以使用我们在第2章节点注释模块所用到的方法来完成它。然后,你需要在 hook_nodeapi(’view’) 内部检查给定节点类型是否启用了投票功能。

兼容性

jQuery 的兼容性,作为 jQuery 的重要信息,你可以在 http://docs.jquery.com 找到。总之,jQuery 支持下面的浏览器:

  • IE6.0 及更高版本
  • Mozilla Firefox 1.5 及更高版本
  • Apple Safari 2.0 及更高版本
  • Opera 9.0 及更高版本

小结

在本章,你学到了:

  • 什么是 jQuery
  • jQuery 如何工作的等一般概念
  • jQuery 和 Drupal 是如何交互的,请求和数值在前后之间的传递
  • 如何构建一个简单的投票小部件

相关文章


没有评论

(*)
(不会公布)