Drupal专业开发指南 第17章 使用jQuery

老葛的Drupal培训班 Think in Drupal

 JavaScript无处不在。每个主流的Web浏览器都带有一个JavaScript解释器。Apple的控制面板小部件就是用JavaScript写的。Mozilla Firefox的用户界面就是使用JavaScript实现的。Adobe Photoshop里面也可以使用JavaScript。JavaScript存在于web技术的每个角落。
     不久以前, JavaScript枯燥无味,很容易让人头痛。如果你曾经有过这样痛苦的经历,现在是时候换种方式了,过去的就让它过去吧,现在我们开始使用jQuery。jQuery使得JavaScript的代码编写更加直观有趣,它还被内置到了Drupal中!在本章,我们将学习什么是jQuery以及在Drupal中如何使用它。最后我们将深入的学习一个实际的例子。
 

Drupal版本:

Drupal专业开发指南 第17章 什么是jQuery?

 

jQuery,是由John Resig创建的,主要用于解决开发者在使用JavaScript时遇到的常见问题和限制。编写JavaScript代码是件麻烦和繁琐的事情,而查找你想操作的特定的HTML或CSS元素有时也非常困难。jQuery为你提供了一种简单快捷的方式,用来在你的文档中查找这些元素。
 定位一个对象的技术名字叫做DOM遍历(traversal)。DOM 是文档对象模型的简称。该模型提供了一种树状方式,通过它们的标签来访问页面元素,以及通过JavaScript访问其它元素,如图17-1所示。
 
注意 你可以从jQuery的官方网站http://jquery.comhttp://www.visualjquery.com 学到更多的jQuery相关知识。
 
    当编写JavaScript代码时,你常常花费大量时间来处理浏览器和操作系统的不兼容性。jQuery为你处理了这一工作。还有,JavaScript中没有太多的高级函数。常见的任务,比如页面特定部分的动画特效,四处拖曳东西,或者可排序的元素,这些在JavaScript中都不存在。而jQuery则克服了这些限制。
 
       和Drupal一样,jQuery的核心代码非常精炼并且高效,仅有30 kb。在jQuery的中心,是一个可扩展的框架,JavaScript程序员可以对其进行扩展,在http://plugins.jquery.com/中已有数百个jQuery插件了。
 
图17-1 http://jquery.com页面的DOM表示,这里使用了Firefox浏览器中的Mozilla DOM检查工具

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第17章 老方式

 

首先,让我们快速的回顾一下纯JavaScript方式的DOM遍历。下面的代码展示了,在引入jQuery以前,Drupal是如何查找页面元素的(在这里,就是所有的可伸缩的字段集内部的legend元素):
 
var fieldsets = document.getElementsByTagName('fieldset');
var legend, fieldset;
for (var i = 0; fieldset = fieldsets[i]; i++) {
    if (!hasClass(fieldset, 'collapsible')) {
        continue;
    }
    legend = fieldset.getElementsByTagName('legend');
    if (legend.length == 0) {
        continue;
    }
    legend = legend[0];
    ...
}
 
         而下面则是在Drupal中引入jQuery以后,升级了的代码:
 
$('fieldset.collapsible > legend:not(.collapse-processed)', context).each(
    function() { ... });
 
    正如你看到的一样, jQuery的口头禅是“写得少,做得多”。对于使用JavaScript操作DOM的常见的重复性任务,jQuery使用一种简洁直观的语法对其作了封装。最终的结果是,代码简短,灵巧,易读。
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第17章 jQuery是如何工作的

 

       jQuery是一个工具,用来在结构化文档中查找东西。可以使用CSS选择器或者jQuery自定义选择器(一个jQuery插件,支持使用XPath选择器),来从文档中选择元素。为DOM遍历使用CSS选择器对于开发者来说很有帮助,因为大多数开发者都已经熟悉了CSS语法。jQuery完整的支持了CSS 1,CSS 2,CSS 3。在我们深入学习如何在Drupal中使用jQuery以前,让我们先看几个基本的例子,来学习一下jQuery语法。

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第17章 使用一个CSS ID选择器

 

让我们快速的回顾一下基本的CSS语法。假定你要操作的HTML如下所示:
 
<p id="intro">Welcome to the World of Widgets.</p>
 
    如果你想将段落的背景颜色设置为蓝色,那么你使用CSS在你的样式表中定位这个特定的段落,这里使用了 #intro ID选择器。根据HTML规范,在一个给定的文档内,ID必须是唯一的,所以我们可以确定只有这个元素拥有这一ID。在应用于你文档的样式表内部,添加以下条目,让你的段落变为蓝色:
 
#intro {
    background-color: blue;
}
 
    注意,这里主要有两个任务:查找具有#intro ID的元素,将该元素的背景颜色设为蓝色。
    使用jQuery,你也可以完成同样的工作。但是首先,我们先简单介绍一下jQuery的语法:为了保持代码的简洁性,在jQuery的JavaScript代码中,使用下面的一行将jQuery命名空间映射为了美元符号($):
 
var jQuery = window.jQuery = function( selector, context ) {...};
...
// Map the jQuery namespace to the '$' one
window.$ = jQuery;
 
注意 如果你对jQuery引擎的工作原理感兴趣的话,你可以下载完整的未压缩的jQuery JavaScript文件,下载地址为http://jquery.com。Drupal中包含的是一个压缩的版本,这样在使用浏览器访问你的站点时,需要下载的数据总量就会小一些。
 
       下面使用jQuery,来选出你的段落并将其背景颜色转变为蓝色:
 
$("#intro").css("background-color", "blue");
 
    你甚至可还以加点jQuery特效,来慢慢的渐进显示段落文本:
 
$("#intro").css("background-color", "blue").fadeIn("slow");
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第17章 使用一个CSS类选择器

 

下面是一个类似的例子,与上节中我们所用的CSS ID选择器不同,这里使用了CSS类选择器。HTML如下所示:
 
<p class="intro">Welcome to the World of Widgets.</p>
<p class="intro">Widgets are available in many sizes.</p>
 
    我们的CSS如下所:
 
.intro {
    background-color: blue;
}
 
    下面的也可以工作,它是一个更加具体的规则:
 
p.intro {
    background-color: blue;
}
 
    下面是对应的jQuery代码:
 
$(".intro").css("background-color", "blue").fadeIn("slow");
$("p.intro").css("background-color", "blue").fadeIn("slow");
 
    在上面的第一个例子中,你让jQuery查找具有info类的任何HTML元素,而第二个例子则有点细微的不同。这里你在所有的段落标签中,查找具有info类的元素。注意后者的速度稍微快了一点,这是因为使用p.intro将查找的范围限制在了段落标签中,从而查找的HTML就少了许多。
 
提示CSS中,“.”是一个类选择器,在同一文档中可以重复出现;而“#”是一个唯一的ID选择器,在同一页面只能出现一次。
 
 现在你对jQuery的工作原理应该有些了解了,让我们实际的看一下如何在Drupal中使用它。
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第17章 Drupal中的jQuery

 

Drupal中使用jQuery很简单,这是因为Drupal内置了jQuery,并且在添加JavaScript时会自动加载jQuery。在Drupal中,通过drupal_add_js()函数来添加JavaScript文件。在本节中,我们将研究Drupal中的一些基本的jQuery功能。

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第17章 你的第一段jQuery代码

 

在使用jQuery以前,让我们先做些准备工作.
 
1. 以用户1的身份(管理帐号)登录到你的Drupal站点。
 
2. 导航到“管理➤站点构建➤模块”,启用PHP过滤器模块。
 
3. 创建一个新的Page类型的节点,在节点创建表单上,在“输入格式”下面一定要选择“PHP代码”,如图17-2所示。在标题中输入Testing jQuery,在表单的正文中输入以下内容:
 
<?php
    drupal_add_js(
        '$(document).ready(function(){
            // Hide all the paragraphs.
            $("p").hide();
            // Fade them into visibility.
            $("p").fadeIn("slow");
        });',
        'inline'
    );
?>
 
<p id="one">Paragraph one</p>
<p>Paragraph two</p>
<p>Paragraph three</p>
 
         点击提交,这样就重新加载了页面。你创建的3个段落将慢慢的渐显出来。酷吧?刷新页面,再看一遍,让我们进一步学习一下这个例子。
 
17-2.使用PHP过滤器来做jQuery实验
 
    jQuery代码包含在文件misc/jquery.js中。并不是为Drupal中的每个页面都加载这个文件。只有在调用了方法drupal_add_js()时,才加载它。这里向drupal_add_js()传递了两个参数。第一个参数是一段你希望执行的JavaScript代码,第二个参数“inline”则用来告诉Drupal将代码写入到文档的<head>元素中的<script></script>标签里面。
 
注意 我们这里只是很简单的使用了drupal_add_js(),它还有许多高级的用法,你可以参看http://api.drupal.org/api/function/drupal_add_js/6
 
    让我们更仔细的看看这段JavaScript jQuery代码。
 
$(document).ready(function(){
    // Hide all the paragraphs.
    $("p").hide();
    // Fade them into visibility.
    $("p").fadeIn("slow");
});
 
    第一行代码需要详细的解释一下。当浏览器显示一个页面时,它会到达一个点----它接收到了HTML并完全的解析了页面的DOM结构。接下来的步骤就是显示DOM,这包括加载附加的本地文件(可能还有远程文件)。如果在生成DOM以前,你想尝试执行JavaScript代码,由于代码想要操作的对象还不存在,那么它可能就会抛出错误并停止运行。JavaScript程序员一般使用下面的代码(或其变体)来处理这种情况:
 
window.onload = function(){ ... }
 
         使用window.onload的缺点是,它需要完全加载所有的附加文件,这可能需要等待很长的时间。另外这种方式的局限性还有,在这里只能使用一个函数。为了解决这两个问题,jQuery有一个简单的语句供你使用:
 
$(document).ready(function(){
// Your code here.
});
 
         在生成了DOM以后,就立即执行$(document).ready()。由于前面所列的原因,你通常需要把你的jQuery代码封装在前面的语句中。function()调用在JavaScript中定义了一个匿名函数----在这里,包含了你想要执行的代码。
 
    好了,让我们看一下代码的中心内容,此时应该就不言自明了:
 
// Hide all the paragraphs.
$("p").hide();
// Fade them into visibility.
$("p").fadeIn("slow");
 
       前面的代码找出了所有的段落标签,隐藏它们,并在页面内部慢慢的把它们显示出来。用jQuery的行话来说,fadeIn()部分就是一个方法
 
注意 我们修改的是所有的段落标签,所以如果你访问一个节点列表页面,比如http://example.com/?q=node,你将发现所有的段落标签都受到了影响,而不仅仅是来自于你测试页面的摘要中的段落。在我们的例子中,可以通过修改我们的node.tpl.php模板文件,当节点单独显示在一个页面时,使用<div class='standalone'>对其内容进行包装,这样就可以使用$(".standalone > p")来限制段落标签集了。这个查询仅选择位于类.standalone内部的p元素。
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第17章 通过ID定位一个元素

 

让我们重复我们的实验,不过这次只定位第一个段落,我们使用ID one对它进行了标识:
 
<?php
    drupal_add_js(
        '$(document).ready(function(){
            // Hide paragraph with ID "one".
            $("#one").hide();
            // Fade it into visibility.
            $("#one").fadeIn("slow");
        });',
        'inline'
    );
?>
 
<p id="one">Paragraph one</p>
<p>Paragraph two</p>
<p>Paragraph three</p>
 
注意 jQuery里面,通过ID访问一个元素是最快的选择器方法之一,这是因为它会被翻译为本地的JavaScript代码:document.getElementById("one ")。替代方案$("p#one")就会慢一些,这是因为jQuery首先需要查找所有的段落标签,然后再从中选择ID为one的元素。在jQuery中最慢的选择器方法就是$(".foo"),因为它需要在所有的元素中查找选择器类为foo的元素(在该情况下,使用$("p.foo")会快一些)。
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第17章 方法链

 

       我们可以连接一系列的jQuery方法的原因是, jQuery内部的大多数方法都返回一个jQuery对象。让我们在单个jQuery命令中将多个方法串连起来:
 
// Hide all the p tags, fade them in to visibility, then slide them up and down.
$("p").hide().fadeIn("slow").slideUp("slow").slideDown("slow");
 
    jQuery的调用是从左向右进行的。前面的代码片段找到所有的段落标签,并渐进的显示它们,然后使用一个幻灯效果把段落移上去再移下来。由于其中的每个方法返回的jQuery包装器对象都包含了相同的元素集(所有的p元素),所以我们可以对同一个元素集进行一次又一次的操作,直到达到最终的效果。
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第17章 添加或删除一个类

 

jQuery可以动态的修改一个元素的CSS类。这里,我们通过ID选中我们例子中的第一个段落,然后为它分配一个Drupal的错误类,这样就将它转变为红色了:
 
$("#one").addClass("error");
 
    与addClass()方法对应的是removeClass()方法。下面的代码片段将删除我们刚刚添加的错误类:
 
$("#one").removeClass("error");
 
    还有一个toggleClass()方法,每次调用它时都会添加或删除一个类:
 
$("#one").toggleClass("error"); // Adds class "error".
$("#one").toggleClass("error"); // Removes class "error".
$("#one").toggleClass("error"); // Adds class "error" again.
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第17章 包装已有元素

 

 
    我们前面介绍了向<p id="one">元素添加一个错误类,现在换种方式,让我们把该元素包装在一个div标签中,这样就能够更好的显示红色了。下面的jQuery片段将实现这一点:
 
<?php
    drupal_add_js(
        '$(document).ready(function(){
           $("#one").wrap("<div class=\'error\'></div>");
        });',
        'inline'
    );
?>
 
<p id="one">Paragraph one</p>
<p>Paragraph two</p>
<p>Paragraph three</p>
 
    注意单引号的转义,由于我们在drupal_add_js()内部已经使用了一对单引号,所以需要对双引号内部的单引号进行转义。div包装的结果如图17-3所示。
 
17-3. ID为“one”的段落被包装在了一个类为“error”的div标签中。
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第17章 修改CSS元素的值

 

可以使用jQuery为CSS元素分配(或重新分配)值。让我们将第一个段落外面的边框设为实线(参看图17-4):
 
$("#one").wrap("<div class=\'error\'></div>").css("border", "solid");
 
    注意,css方法作用的仍然是p元素,而不是div元素,这是因为wrap方法在完成了包装以后返回的仍然是原有的p元素。
 
17-4.目标元素的边框属性已被修改
 
    前面的这些例子说明了jQuery可以实现的一些基本任务,这里仅仅涉及到了jQuery的一点皮毛。如果你需要这方面的更多知识,那么可以访问http://jquery.com/,或者找一本好的jQuery书籍来好好的学习一下。
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第17章 JavaScript放在哪里

 

在前面的例子中,我们启用了PHP过滤器模块,通过直接在节点中编写JavaScript来测试jQuery。这种方式用于测试还是比较方便的,但是如果放在实际的站点上,就不能用了,最佳实践表明要尽可能的不用PHP过滤器。有多种不同的选择,可以用来在你的Drupal站点上包含JavaScript文件。例如,你可以把它们添加到你的主题中,可以从模块中包含它们,甚至可以在包含它们的同时允许其它模块修改或覆写你的代码。
 

老葛的Drupal培训班  Think in Drupal

Drupal版本:

Drupal专业开发指南 第17章 通过一个主题.info文件添加JavaScript

 

包含JavaScript文件的最方便的方式,就是在你的主题的.info文件中添加一行代码,不过这种方式也有一个缺点,那就是缺乏灵活性。让我们为你的站点添加一个效果,用来强调你站点的标识语,在一个页面被加载时,先把它淡出,接着再把它渐显出来。把下面的JavaScript代码放在你主题下的一个名为logofade.js的文件中。例如,如果你使用的主题为Garland,那么它就位于themes/garland/logofade.js。
 
// $Id$
// Selects the theme element with the id "logo", fades it out,
// then fades it in slowly.
if (Drupal.jsEnabled) {
    $(document).ready(function(){
        $("#logo").fadeOut("fast").fadeIn("slow");
    });
}
 
    JavaScript文件已经有了;现在我们只需要告诉Drupal加载它就可以了。向你的当前主题的.info文件中添加下面一行代码:
 
scripts[] = logofade.js
 
    最后一步就是让Drupal重读.info文件,这样它就会看到它需要加载logofade.js了。为了实现这一点,导航到“管理➤站点构建➤主题”,临时的转换到一个不同的主题上,然后再转换回来。
    如果对于你网站的每个页面,都需要为其加载某一JavaScript文件的话,那么这个方法还是非常有用的。在接下来的一节中,你将看到如何实现,只有当使用JavaScript的模块被启用时,才加载它。

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第17章 一个使用jQuery的模块

 

让我们构建一个小模块,用来在一个JavaScript文件中包含一些jQuery函数。首先,我们需要一个用例。嗯,使用JavaScript代码来控制区块,如何?区块在Drupal中是很有用的:它们可以为你显示你的登录状态,告诉你站点的新进用户或者在线用户,提供有用的导航。但是有时候,你只想关注页面的内容。如果在默认的情况下把区块隐藏,只有当你想要查看它们的时候才将其显示出来,这样不也不错么?下面的模块实现了这一点,它使用jQuery来对左右边栏区域中的区块进行标识和隐藏,并提供了一个有用的按钮用来将区块重新显示出来。
    下面是sites/all/modules/custom/blockaway.info:
 
; $Id$
name = Block-Away
description = Uses jQuery to hide blocks until a button is clicked.
package = Pro Drupal Development
core = 6.x
 
    而下面是sites/all/modules/custom/blockaway.module:
 
<?php
// $Id$
 
/**
 * @file
 * Use this module to learn about jQuery.
 */
 
/**
 * Implementation of hook_init().
 */
function blockaway_init() {
    drupal_add_js(drupal_get_path('module', 'blockaway') .'/blockaway.js');
}
 
    这个模块的全部工作就是包含以下的JavaScript文件,我们可以将它放在sites/all/modules/custom/blockaway/blockaway.js:
 
// $Id$
 
/**
 * Hide blocks in sidebars, then make them visible at the click of a button.
 */
if (Drupal.jsEnabled) {
    $(document).ready(function() {
        // Get all div elements of class 'block' inside the left sidebar.
        // Add to that all div elements of class 'block' inside the
        // right sidebar.
        var blocks = $('#sidebar-left div.block, #sidebar-right div.block');
 
        // Hide them.
        blocks.hide();
 
        // Add a button that, when clicked, will make them reappear.
        $('#sidebar-left').prepend('<div id="collapsibutton">Show Blocks</div>');
        $('#collapsibutton').css({
            'width': '90px',
            'border': 'solid',
            'border-width': '1px',
            'padding': '5px',
            'background-color': '#fff'
        });
        // Add a handler that runs once when the button is clicked.
        $('#collapsibutton').one('click', function() {
            // Button clicked! Get rid of the button.
            $('#collapsibutton').remove();
            // Display all our hidden blocks using an effect.
            blocks.slideDown("slow");
        });
    });
}
 
    导航到“管理➤站点构建➤模块”,启用该模块,你以前可见的所有区块都消失了,被替换为了一个没有格式的按钮,如图17-5所示。
 
17-5. blockaway.module启用后,一个节点的显示
 
    点击了这个按钮以后,区块将会使用一个幻灯效果显示出来,变成可见的,如图17-6所示。
 
17-6.点击了显示区块按钮以后,区块变成了可见的。
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第17章 可覆写的JavaScript

老葛的Drupal培训班 Think in Drupal

    blockaway.module中的代码非常简单并易于理解。它只负责包含blockaway.js文件。然而,如果模块更加复杂一点的话,那么将drupal_add_js()函数调用放在一个主题函数中,来取代放在hook_init()中,这对其他开发者来说会更加友好。这样,对于那些想使用你的模块,同时又想用一些方式来定制JavaScript代码的用户,他们无需修改你模块的源代码就可以完成定制工作(关于主题系统的更多详细,可参看第8章)。下面的代码是blockaway.module的修订版本,它使用hook_theme()声明了一个主题函数,并将drupal_add_js()调用移到了该主题函数中,而在hook_init()中调用这个主题函数。功能是一样的,但是现在聪明的开发者就可以对blockaway.js文件进行覆写了。
 
<?php
// $Id$
 
/**
 * @file
 * Use this module to learn about jQuery.
 */
 
/**
 * Implementation of hook_init().
 */
function blockaway_init() {
    theme('blockaway_javascript');
}
 
/**
 * Implementation of hook_theme().
 * Register our theme function.
 */
function blockaway_theme() {
    return array(
        'blockaway_javascript' => array(
            'arguments' => array(),
        ),
    );
}
 
/**
 * Theme function that just makes sure our JavaScript file
 * gets included.
 */
function theme_blockaway_javascript() {
    drupal_add_js(drupal_get_path('module', 'blockaway') .'/blockaway.js');
}
 
    让我们继续前进,来看看这种方法是否可以工作。我们将使用主题提供的JavaScript,来覆写模块提供的JavaScript。把sites/all/modules/custom/blockaway/blockaway.js拷贝到你的当前主题下----例如,themes/garland/blockaway.js。让我们稍微的修改一下JavaScript文件,这样我们就知道正在使用的是哪个JavaScript文件。将特效从slideDown("slow")改为fadeIn(5000);这将使用5秒钟的时间来渐进的显示区块。下面是新文件:
 
// $Id$
/**
 * Hide blocks in sidebars, then make them visible at the click of a button.
 */
if (Drupal.jsEnabled) {
    $(document).ready(function() {
        // Get all div elements of class 'block' inside the left sidebar.
        // Add to that all div elements of class 'block' inside the
        // right sidebar.
        var blocks = $('#sidebar-left div.block, #sidebar-right div.block');
 
        // Hide them.
        blocks.hide();
 
        // Add a button that, when clicked, will make them reappear.
        // Translate strings with Drupal.t(), just like t() in PHP code.
        var text = Drupal.t('Show Blocks');
        $('#sidebar-left').prepend('<div id="collapsibutton">' + text + '</div>');
        $('#collapsibutton').css({
            'width': '90px',
            'border': 'solid',
            'border-width': '1px',
            'padding': '5px',
            'background-color': '#fff'
        });
 
        // Add a handler that runs once when the button is clicked.
        $('#collapsibutton').one('click', function() {
            // Button clicked! Get rid of the button.
            $('#collapsibutton').remove();
            // Display all our hidden blocks using an effect.
           blocks.fadeIn(5000);
        });
    });
}
 
    我们最后要做的修改就是告诉Drupal加载这个新文件,从而取代sites/all/modules/custom/blockaway中的文件。我们通过覆写主题函数来实现这一点。向你主题的template.php文件中(如果你的主题还没有一个template.php文件,那么你需要创建一个),添加下面所给的函数:
 
<?php
// $Id$
 
/**
 * Override theme_blockaway_javascript() with the
 * following function.
 */
function phptemplate_blockaway_javascript() {
    drupal_add_js(path_to_theme() . '/blockaway.js');
}
 
    现在,当你使用浏览器来访问一个页面时,你应该能够看到“显示区块”按钮了,点击这个按钮,将会使用一个渐进的淡入效果来显示区块,而不是我们前面所用的幻灯效果。恭喜恭喜!你已经学会了如何在你的模块中使用jQuery,如何使用友好的方式编写jQuery以方便主题制作者和其他的开发者,同时,你还学会了如何覆写或增强其它模块中的JavaScript文件,这里假定这些模块中的JavaScript文件可被覆写。
    在我们结束这个例子的学习以前,让我演示一下如何使用模板文件进行覆写。首先,删除你添加到template.php文件中的phptemplate_blockaway_javascript()函数。接着,在你的当前主题中,创建一个空文件blockawayjavascript.tpl.php。例如,如果你使用的是Garland主题,那么创建的就是themes/garland/blockaway-javascript.tpl.php。不要在这个文件中放置任何东西。现在导航到“管理➤站点构建➤模块”,访问这个页面的作用就是重新构建主题注册表。Drupal将找到该模板文件,并使用它来替代你模块中的主题函数。最终的结果就是永远也不会加载blockaway.js了;通过创建一个空的模板文件,你实质上就是注释掉了该主题函数(回想一下第8章所讲的,当构建主题注册表的时候,Drupal将首先查找一个模板文件,然后才是主题函数)。
    现在,向你的blockaway-javascript.tpl.php文件中添加以下代码:
 
<?php drupal_add_js(path_to_theme() . '/blockaway.js'); ?>
 
    当你重新加载页面时,你应该可以看到JavaScript文件现在加载进来了。如果你想将第3方模块中的JavaScript文件替换为你的增强版本,或者想阻止加载一些JavaScript文件时,这里所讲的这些技术还是很有用处的。
 
注意 你不能在page.tpl.php中调用drupal_add_js(),对于其它的一些主题函数,如果是在它的预处理阶段调用的话(比如区块),那么也不能使用drupal_add_js()。为什么呢?这是因为它们在页面构建流程中执行的顺序过于靠后。核心模板文件是如何添加JavaScript的,可参看modules/block/block-admin-display-form.tpl.php。
 

Drupal版本:

Drupal专业开发指南 第17章 构建一个jQuery投票小部件(Widget)

老葛的Drupal培训班 Think in Drupal

让我们编写一个稍微复杂一点的启用jQuery的Drupal模块。我们将构建一个AJAX投票小部件,如图17-7所示,它可以让用户为他们喜欢的文章加一分。我们将使用jQuery来进行投票和修改总的投票分数,而不用重新加载整个页面。我们还添加一个基于角色的权限,这样只有具有“rate content”权限的用户才允许投票。由于用户的每次投票只能增加一分,所以让我们将模块命名为“plusone”。
 
17-7.投票小部件
 
    在我们开始为plusone模块编写实际的jQuery部分以前,首先需要构建模块所需的一些基本代码。如果你以前从来没有构建过模块,请参看第2章。否则,那么就让我们现在开始吧!
    在sites/all/modules/custom中创建一个名为plusone的目录(你可能首先需要创建sites/all/modules/custom目录)。在plusone目录中,创建文件plusone.info,向里面添加以下内容:
 
; $Id$
name = Plus One
description = "A +1 voting widget for nodes. "
package = Pro Drupal Development
core = 6.x
 
    这个文件将该模块注册到了Drupal中,这样可以通过管理界面启用或者禁用模块了。
    接着,我们将创建plusone.install文件。当启用、禁用、安装、卸载这个模块时,就会调用这个PHP文件中的函数;它一般用来创建或者删除数据库表。在这里,我们想用来追踪谁在哪个节点上投了票:
 
<?php
// $Id$
 
/**
 * Implementation of hook_install().
 */
function plusone_install() {
    // Create tables.
    drupal_install_schema('plusone');
}
 
/**
 * Implementation of hook_uninstall().
 */
function plusone_uninstall() {
    // Remove tables.
    drupal_uninstall_schema('plusone');
}
 
/**
 * Implementation of hook_schema().
 */
function plusone_schema() {
    $schema['plusone_votes'] = array(
        'description' => t('Stores votes from the plusone module.'),
        'fields' => array(
            'uid' => array(
                'type' => 'int',
                'not null' => TRUE,
                'default' => 0,
                'description' => t('The {user}.uid of the user casting the vote.'),
            ),
            'nid' => array(
                'type' => 'int',
                'not null' => TRUE,
                'default' => 0,
                'description' => t('The {node}.nid of the node being voted on.'),
            ),
            'vote_count' => array(
                'type' => 'int',
                'not null' => TRUE,
                'default' => 0,
                'description' => t('The number of votes cast.'),
            ),
        ),
        'primary key' => array('uid', 'nid'),
        'indexes' => array(
            'nid' => array('nid'),
            'uid' => array('uid'),
        ),
    );
    return $schema;
}
 
    还有,添加文件sites/all/modules/custom/plusone/plusone.css。这个文件不是必需的,但它可以使投票小部件的外观更漂亮一些,如图17-8所示。
 
17-8. 对比带有CSS投票小部件和不带有CSS的投票小部件
 
    向plusone.css添加以下内容:
 
div.plusone-widget {
    width: 100px;
    margin-bottom: 5px;
    text-align: center;
}
div.plusone-widget .score {
    padding: 10px;
    border: 1px solid #999;
    background-color: #eee;
    font-size: 175%;
}
div.plusone-widget .vote {
    padding: 1px 5px;
    margin-top: 2px;
    border: 1px solid #666;
    background-color: #ddd;
}
 
    我们已经创建了外围支持文件,现在让我们关注模块文件本身和jQuery JavaScript文件。创建两个空文件,sites/all/modules/custom/plusone/plusone.js和sites/all/modules/custom/plusone/plusone.module,在接下来的几步中,我们将逐步的向这两个文件添加代码。总结一下,我们已经创建了以下文件:
 
sites/
    all/
        modules/
            custom/
                plusone/
                    plusone.js
                    plusone.css
                    plusone.info
                    plusone.install
                    plusone.module

Drupal版本:

Drupal专业开发指南 第17章 创建模块

老葛的Drupal培训班 Think in Drupal

在一个文本编辑器中打开空的plusone.module,并添加标准的Drupal头部文件:
 
<?php
// $Id$
/**
 * @file
 * A simple +1 voting widget.
 */
 
    接下来我们将逐个的添加我们用到的Drupal钩子。一个比较简单的就是hook_perm(),它让你向Drupal的基于角色的访问控制页面添加一个“rate content”权限。使用这一权限,你就可以阻止那些没有账号或者没有登录的匿名用户进行投票了。
 
/**
 * Implementation of hook_perm().
 */
function plusone_perm() {
    return array('rate content');
}
 
    现在,我们开始实现一些AJAX功能。jQuery的一个重要特性,就是它能够提交自己的HTTP GET 或 POST请求,使用这一特性你就可以将投票提交给Drupal,而不用刷新整个页面了。jQuery将拦截投票链接上的点击事件,并向Drupal发送一个请求,让其保存投票并返回更新后的总分数。jQuery将使用这个新值来更新页面上的分数。图17-9展示了整个流程的概览。
    一旦jQuery拦截了对投票链接的点击,它需要通过一个URL来调用一个Drupal函数。我们使用hook_menu(),将由jQuery提交的投票URL映射到一个Drupal PHP函数上。这个PHP函数将投票保存到数据库中,并以JSON (JavaScript Object Notation)的形式为jQuery返回一个新分数(噢,这样我们就没有使用XML,因此这也就不是严格意义上的AJAX了)。
 
/**
 * Implementation of hook_menu().
 */
function plusone_menu() {
    $items['plusone/vote'] = array(
        'page callback' => 'plusone_vote',
        'access arguments' => array('rate content'),
        'type' => MENU_CALLBACK,
    );
    return $items;
}
 
    在前面的函数中,当路径为plusone/vote的请求进来以后,如果请求该路径的用户拥有“rate content”权限,那么函数plusone_vote()就会处理这个请求。
   
17-9.投票更新流程概览
 
注意 如果发送请求的用户没有“rate content”权限,Drupal将返回一个拒绝访问页面。然而,我们将确保动态的构建我们的投票小部件,这样那些无权投票的用户就看不到投票链接了。对于那些恶意的用户,他们可能会绕过我们的小部件,直接访问http://example.com/?q=plusone/vote,此时Drupal的权限系统仍然会保护我们免受他们的攻击。
 
    路径plusone/vote/3翻译成了PHP函数调用plusone_vote(3)(关于Drupal的菜单/回调系统的更多详细,参看第4章)。
 
/**
 * Called by jQuery, or by browser if JavaScript is disabled.
 * Submits the vote request. If called by jQuery, returns JSON.
 * If called by the browser, returns page with updated vote total.
 */
function plusone_vote($nid) {
    global $user;
    $nid = (int)$nid;
 
    // Authors may not vote on their own posts. We check the node table
    // to see if this user is the author of the post.
    $is_author = db_result(db_query('SELECT uid FROM {node} WHERE nid = %d AND
        uid = %d', $nid, $user->uid));
 
    if ($nid > 0 && !$is_author) {
        // Get current vote count for this user.
        $vote_count = plusone_get_vote($nid, $user->uid);
        if (!$vote_count) {
        // Delete existing vote count for this user.
        db_query('DELETE FROM {plusone_votes} WHERE uid = %d AND nid = %d',
            $user->uid, $nid);
        db_query('INSERT INTO {plusone_votes} (uid, nid, vote_count) VALUES
            (%d, %d, %d)', $user->uid, $nid, $vote_count + 1);
        watchdog('plusone', 'Vote by @user on node @nid.', array(
            '@user' => $user->name, '@nid' => $nid));
        }
    }
    // Get new total to display in the widget.
    $total_votes = plusone_get_total($nid);
    // Check to see if jQuery made the call. The AJAX call used
    // the POST method and passed in the key/value pair js = 1.
    if (!empty($_POST['js'])) {
        // jQuery made the call.
        // This will return results to jQuery's request.
        drupal_json(array(
            'total_votes' => $total_votes,
            'voted' => t('You voted')
        )
    );
    exit();
    }
 
    // It was a non-JavaScript call. Redisplay the entire page
    // with the updated vote total by redirecting to node/$nid
    // (or any URL alias that has been set for node/$nid).
    $path = drupal_get_path_alias('node/'. $nid);
    drupal_goto($path);
}
 
    前面的plusone_vote()函数,保存了当前的投票,并向jQuery返回信息;信息的形式是一个关联数组,里面包含了新总分和字符串You voted(你已投票),这个字符串用于替换投票小部件下面的Vote(投票文本。这个数组将被传给drupal_json(),这样就可以将PHP变量转化为它们的JavaScript等价物,在这里,就是将一个PHP关联数组转化为了一个JavaScript对象,并将HTTP头部设置为Content-type: text/javascript。关于JSON的更多详细,参看http://en.wikipedia.org/wiki/JSON
    注意,在前面的函数中,我们是如何处理浏览器禁用了JavaScript这种情况的。当我们编写jQuery代码时,我们将确保,来自jQuery的AJAX调用会传递一个名为js的参数并使用POST方法。如果没有js参数的话,我们就知道此时是用户点击了投票链接,浏览器本身请求了该路径----例如,plusone/vote/3。在这种情况下,因为浏览器期望的是一个普通的HTML页面,所以我们不会返回JSON。相反,我们更新了投票总分来反映用户投票的事实,接着,我们将浏览器重定向到最初的页面,Drupal将负责重新构建该页面并显示新的投票总分。
    在前面的代码中,我们调用了plusone_get_vote()和plusone_get_total(),现在让我们创建这两个函数:
 
/**
 * Return the number of votes for a given node ID/user ID pair.
 */
function plusone_get_vote($nid, $uid) {
    return (int)db_result(db_query('SELECT vote_count FROM {plusone_votes} WHERE
        nid = %d AND uid = %d', $nid, $uid));
}
 
/**
 * Return the total vote count for a node.
 */
function plusone_get_total($nid) {
    return (int)db_result(db_query('SELECT SUM(vote_count) FROM {plusone_votes}
        WHERE nid = %d', $nid));
}
 
    现在,让我们集中精力将投票小部件显示在文章旁边。这包括两部分。首先,我们在plusone_widget()函数内部定义一些变量。接着我们将这些变量传递给一个主题函数。下面是第一部分:
 
/**
 * Create voting widget to display on the web page.
 */
function plusone_widget($nid) {
    global $user;
 
    $total = plusone_get_total($nid);
    $is_author = db_result(db_query('SELECT uid FROM {node} WHERE nid = %d
        AND uid = %d', $nid, $user->uid));
    $voted = plusone_get_vote($nid, $user->uid);
 
    return theme('plusone_widget', $nid, $total, $is_author, $voted);
}
 
    还记不记得,当我们需要一个可主题化的项目时,我们需要使用hook_theme()来向Drupal声明它,这样就将其包含在了主题注册表中。下面就是这个钩子函数:
 
/**
 * Implementation of hook_theme().
 * Let Drupal know about our theme function.
 */
function plusone_theme() {
    return array(
        'plusone_widget' => array(
            'arguments' => array('nid', 'total', 'is_author', 'voted'),
        ),
    );
}
 
    接着,我们就需要实际的主题函数了。注意,在这里我们加载了我们的JavaScript和CSS文件。
 
/**
 * Theme for the voting widget.
 */
function theme_plusone_widget($nid, $total, $is_author, $voted) {
    // Load the JavaScript and CSS files.
    drupal_add_js(drupal_get_path('module', 'plusone') .'/plusone.js');
    drupal_add_css(drupal_get_path('module', 'plusone') .'/plusone.css');
 
    $output = '<div class="plusone-widget">';
    $output .= '<div class="score">'. $total .'</div>';
 
    $output .= '<div class="vote">';
    if ($is_author) {
        // User is author; not allowed to vote.
        $output .= t('Votes');
    }
    elseif ($voted) {
        // User already voted; not allowed to vote again.
        $output .= t('You voted');
    }
    else {
        // User is eligible to vote.
        $output .= l(t('Vote'), "plusone/vote/$nid", array(
            'attributes' => array('class' => 'plusone-link')
            ));
    }
    $output .= '</div>'; // Close div with class "vote".
    $output .= '</div>'; // Close div with class "plusone-widget".
 
    return $output;
}
 
    在前面代码的plusone_widget()函数中,我们先设置了一些变量,接着将小部件的主题化委托给了我们创建的自定义主题函数theme_plusone_widget()。记住theme('plusone_widget')实际上调用的就是theme_plusone_widget()(更多详细,可参看第8章)。创建一个单独的主题函数,而不是在plusone_widget()函数内部构建HTML,这样设计者在想要修改外观时就能覆写这个函数了。
    在我们的主题函数theme_plusone_widget()中,一定要为关键的HTML元素添加CSS类属性,这样在jQuery中就可以非常方便的定位这些元素了。还有,看一下链接的URL。它指向了plusone/vote/$nid,其中$nid是文章的当前节点ID。当用户点击这个链接时,由于我们使用jQuery监听该链接上的onClick事件,所以jQuery将代替Drupal来拦截并处理这个事件。看到没有,我们在构建链接时,是如何定义CSS选择器plusone-link的?在我们后面的Javascript中,找到该选择器出现的地方,这就是a.plus1-link。它就是说,一个带有css类plusone-link的HTML元素<a>。
    显示在页面http://example.com/?q=node/4中的小部件的HTML,应该是这样的:
 
<div class="plusone-widget">
    <div class="score">0</div>
    <div class="vote">
        <a class="plusone-link" href="/plusone/vote/4">Vote</a>
    </div>
</div>
 
    theme_plusone_widget()函数用来生成发送给浏览器的小部件。我们想让这个小部件显示在节点视图中,这样当用户查看节点时,就可以进行投票了。你能猜一下,我们将使用哪个Drupal钩子呢?这就是我们的老朋友hook_nodeapi(),它允许我们修改正被构建的任意节点。
 
/**
 * Implementation of hook_nodeapi().
 */
function plusone_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['plusone_widget'] = array(
                '#value' => plusone_widget($node->nid),
                '#weight' => 100,
            );
        }
        break;
 
        case 'delete':
        // Node is being deleted; delete associated vote data.
        db_query('DELETE FROM {plusone_vote} WHERE nid = %d', $node->nid);
        break;
    }
}
 
    我们将weight元素的值设置为一个大的(或者“重的”)数字,这样就确保了小部件显示在文章的底部,而不是顶部。我们还偷偷的加了一个delete情况,这样当节点被删除时,该节点的投票记录也将被一同删除。
这就是plusone.module的全部内容了。我们的模块马上就要完工了,现在就剩下填写plusone.js了,在里面填写我们的jQuery代码,用于执行AJAX调用,更新投票总分,并将字符串Vote修改为You voted
 
// $Id$
 
// Only run if we are in a supported browser.
if (Drupal.jsEnabled) {
    // Run the following code when the DOM has been fully loaded.
    $(document).ready(function () {
        // Attach some code to the click event for the
        // link with class "plusone-link".
        $('a.plusone-link').click(function () {
            // When clicked, first define an anonymous function
            // to the variable voteSaved.
            var voteSaved = function (data) {
                // Update the number of votes.
                $('div.score').html(data.total_votes);
                // Update the "Vote" string to "You voted".
                $('div.vote').html(data.voted);
            }
            // Make the AJAX call; if successful the
            // anonymous function in voteSaved is run.
            $.ajax({
                type: 'POST', // Use the POST method.
                url: this.href,
                dataType: 'json',
                success: voteSaved,
                data: 'js=1' // Pass a key/value pair.
            });
            // Prevent the browser from handling the click.
            return false;
        });
    });
}
 
    你应该把你所有的jQuery代码都包装在一个Drupal.jsEnabled测试中。这个测试将确保当前的浏览器支持特定的DOM方法(如果不支持的话,那么将不会执行我们的JavaScript)。
    这个JavaScript向a.plusone-link添加了一个事件侦听器(还记不记得我们将plusone-link定义为了CSS类选择器?),这样当用户点击链接时,它将触发一个HTTP POST请求,来请求它指向的URL。前面的代码还演示了,jQuery是如何处理从Drupal中传递回来的数据的。当AJAX请求完成以后,返回值(从Drupal中返回)将作为data参数传递到匿名函数中,我们把这个函数赋值给了变量voteSaved。关联数组中的键所引用的数组,最初是在Drupal内部的plusone_vote()函数中构建的。最后,Javascript更新了分数,并将文本“Vote”修改为了“You Voted”。
    为了阻止加载整个页面(因为JavaScript负责处理点击事件),在JavaScript jQuery函数中,我们把返回值设置为了false

Drupal版本:

Drupal专业开发指南 第17章 使用Drupal.behaviors

 

JavaScript的交互,是通过向DOM中的元素绑定一些行为实现的(例如,使用一个事件,比如鼠标点击,来触发动作)。对DOM的一个修改,可能就会导致这一绑定的失效。所以,尽管我们前面所用的plusone.js文件,在一个简单的Drupal站点上可以很好的工作,但是在一个复杂的Drupal站点上,如果其它JavaScript文件也操作这个DOM的话,那么就可能会遇到问题。Drupal提供了一个中心变量Drupal.behaviors,使用它就可以注册JavaScript函数了,这样就确保了绑定的行为在需要的时候能正确的执行。下面的plusone.js版本,和前面的版本在功能上是一样的,都允许通过AJAX进行投票,但是这里的这个,通过使用Drupal.behaviors进行了注册,从而保护了我们的绑定:
 
// $Id$
 
Drupal.behaviors.plusone = function (context) {
    $('a.plusone-link:not(.plusone-processed)', context)
    .click(function () {
        var voteSaved = function (data) {
            $('div.score').html(data.total_votes);
            $('div.vote').html(data.voted);
        }
        $.ajax({
            type: 'POST',
            url: this.href,
            dataType: 'json',
            success: voteSaved,
            data: 'js=1'
        });
        return false;
    })
    .addClass('plusone-processed');
}
 
    注意,这里我们没有进行Drupal.jsEnabled测试,这是因为现在Drupal能自动的帮我们实现了这一点。关于Drupal.behaviors的更多详细,可参看misc/drupal.js。
 老葛的Drupal培训班 http://www.thinkindrupal.com

Drupal版本:

Drupal专业开发指南 第17章 扩展这个模块的方式

 

    对于这个模块,我们可以进行很多好的扩展,比如允许站点管理员仅对特定节点类型启用投票小部件。实现方式和我们在第2章构建注释模块时所用的方式一样。接着,在hook_nodeapi('view')内部,在添加小部件以前,你应该需要检查一下是否为给定节点类型启用了投票功能。还可以从许多其它的方面来扩展这个模块,比如基于不同的角色为投票分配不同的权数,或者限定一个用户在一天内的总投票数量。我们这里的目的是让这个模块尽可能的简单一点,以强调Drupal与jQuery之间的交互。
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第17章 兼容性

 

jQuery的兼容性,还有jQuery的其它重要信息,都可在http://docs.jquery.com找到。总之,jQuery支持以下浏览器:
 
    • IE6.0及更高版本
    • Mozilla Firefox 1.5及更高版本
    • Apple Safari 2.0.2及更高版本
    • Opera 9.0及更高版本
 
    关于浏览器兼容性的更多详细,可参看http://docs.jquery.com/Browser_Compatibility
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第17章 后续步骤

 

    为了更好的学习如何在Drupal中使用jQuery,可以看一下你的Drupal安装中的misc目录。在这里你可以找到各种JavaScript文件,比如,表单字段的自动完成(autocomplete.js),批处理(batch.js),字段集的伸缩性(collapse.js),进度条的创建(progress.js),可拖拽的表格(tabledrag.js),以及更多。还可参看Drupal的JavaScript小组,位于http://groups.drupal.org/javascript
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第17章 总结

 

在本章中,你学到了:
    • 什么是jQuery
    • jQuery工作原理的一般概念
    • 如何在你的模块中加载JavaScript文件
    • jQuery和Drupal是如何交互的,如何在前后台之间传递请求和数值
    • 如何构建一个简单的投票小部件
 
 
 
 
 
 
 
 
   
 
提示: 如果你在计算机上自己动手实践,但是小部件却不能正常工作。你需要检查一下,你是不是以内容创建者的身份登录了(这是因为用户不能对自己创建的内容投票),并检查一下登录用户是否拥有权限“rate content”(“评论内容”)。一个好用的Ajax请求测试工具是名为Firbug的FireFox插件,你可以从http://getfirebug.com/下载到它。
 
 

老葛的Drupal培训班 Think in Drupal

Drupal版本: