跳转到主要内容
rli 提交于 14 May 2012

原文链接:http://drupal.org/node/933976

一个好的模块会允许它显示的所有元素都能被站点所使用的主题所重写。模块必须被设计成能够被主题所重写来达到此目的。

首先要把逻辑和显示尽可能的分开。要达到这点,模块需要尽可能的把工作坐在数据上,然后把数据传给drupal的表示层。模块可以提供一个默认的显示模板来为以后希望更改显示的主题来提供一个标准。这是通过theme()方法来实现的。每一条被打印出的内容都是通过theme()方法在theme hook中调用并显示的。有两种方法能提供默认的显示。最简单的方法是建立一个方法来打印显示。我们推荐的方法是提供一个template模板和相应的preprocessor方法来显示。我们将把这条展开一点来讲。

最理想的情况是我们的模块不会打印任何HTML代码,把HTML代码全部用theme()方法来打印。在现实中,管理页面和其他一些非常小的条目可能不会通过theme()来打印,但是能简单的用通过theme()方法实现的表示层更改主题,是一个好模块的标准。

注册theme hook

要引用一个theme hook,您的模块首先需要注册这个hook。(这是从D6开始的新内容,由于自动检测更改的方法需要已定的时间,所以我们必须注册我们的theme hook。注册之后,只有在应用到这个hook时,它才会被发现,之后,它就一直可以应用了。)

您的模块的hook_theme()将会返回您模块应用到的所有theme hook的队列,一个简单的hook_theme()如下: <?php function forum_theme() { return array( 'forums' => array( 'template' => 'forums', 'variables' => array('forums' => NULL, 'topics' => NULL, 'parents' => NULL, 'tid' => NULL, 'sortby' => NULL, 'forum_per_page' => NULL), ), //... ); } ?> 这个hook告诉我们一个叫做forums的hook将被执行。默认的执行方式是template。在这里不用包括扩展名,因为Drupal7默认支持PHPTemplate的引擎,所以template文件将会有'.tpl.php'的扩展名。

这段代码还告诉我们forum主题将用到6个参数。他们的默认值都是null。这些参数将以相同的名字被应用到template文件中。当调用这个theme hook时,调用者可以传入:

<?php $output = theme('forums', array('forums' => $forums, 'topics' => $topics, 'parents' => $parents, 'tid' => 17, 'sortby' => 'ASC', 'forums_per_page' => 25)); ?>

如果在hook_theme中忘记了声明template, 那么在执行调用的时候,drupal将默认调用名字为‘theme_forums'的方法。

在这里还有可以设定一些别的参数,但是这两个是最重要的。想了解更多的信息,请参考hook_theme文档。

执行默认的模板

当我们需要 .tpl.php file来执行一个模板。这个文件应该和模块文件放在同一个根目录下。当然,我们一般将放在根目录的主题子目录里。

模板应该尽可能的用纯HTML来编写。但是有些外部命令和方法在这里是推荐使用的。首先是t()方法。模块应该一直提供适当的翻译,主题也不例外。主题的设计者将直接用字符来设计,翻译者需要用t()方法来编译字符,所以我们推荐使用。

另一个推荐使用的方法是format_date()。这个方法就是一个表示方法,我们通常把它用在表示层。但是对于不熟悉PHP的用户,这个方法比较难以理解。无论如何,我们还是要在模板文件里用到这个文件。

对于其他方法,我们要考虑是否真正需要在表示层里应用。如果不是必须的,我们可以把它放到预处理层。所有的模板都有一个可以选择的预处理方法,叫做template_preprocess_HOOK。比如,上面的forum主题,它的预处理方法将叫做template_preprocess_forums()

预处理方法的目的是执行一些逻辑运算来是数据更好和更安全的显示。这对您安全的显示数据和排除XSS (Cross Site Scripting) 漏洞极为重要。因为被显示的数据通常来自于用户,数据必须在显示以前被检查。我们假设主题的作者并不是意义上的程序员,所以我们必须假设主题的设计者并不完全懂得这里面的工作原理。但是这没有关系,因为我们在预处理阶段就已经用check_plain,check_markup, filter_xss_admin等方法检查了数据。

下面是一个在poll模块里的例子:

<?php function template_preprocess_poll_bar(&$variables) { if ($variables['block']) { $variables['theme_hook_suggestions'][] = 'poll_bar__block'; } $variables['title'] = check_plain($variables['title']); $variables['percentage'] = round($variables['votes'] * 100 / max($variables['total_votes'], 1)); } ?>

首先,注意preprocessor方法用到了一个variable的数组。这个数组将作为我们传入theme()方法的参数,并被我们注册钩子是的参数所命名。由于这是实时参数,简单的修改这个参数就可以让模板应用到新的修改。

这个例子阐明了一下三个重要概念:

  • 'title'的内容是不安全的,因为它来自用户的录入。用check_plain方法来检查内容,这样模板可以安全的输出显示。
  • theme hook接受投票的总数和每一项的票数,但是模板需要显示百分比。这里面的计算工作不能在模板代码中完成,所以我们需要在这里来计算。由于variables数组始终不变,所以如果主题设计者想要重写显示别的(非百分数),也可以简单的完成。
  • 特殊的变量theme_hook_suggestions能被用来提供可选择的模板文件。这是一个包含了hook名的栈,才用先进先出原则。也就是说最后被添加进数组的元素将最先被采用。如果一个模板未找到,那将会自动采用下一个模板。这个例子中的双下划线表明了这点。它并不是在查找带有双下划线的hook或者模板,在这里,它首先查找poll-bar-block.tpl.php,如果失败则省略下划线后边的部分,进而查找poll-bar.tpl.php

Theme Developer module, 是devel模块的一部分,他包括一个模块log的功能,能在页面底部显示当先页面所用到的所有模板。这在您创建模块时很有帮助,在您设计主题时更有帮助。另外,不要忽视它自动跳出的主题信息。

笔记:模板文件的命名将使用横线‘-’。如果theme hook是‘forum_list‘,那么模板文件将被命名为‘forum-list.tpl.php‘。另外,如果您打算用到preprocess方法,在theme hook名中不要用横线。

执行theme方法

drupal允许在您默认的主题应用中使用方法。这比使用template文件更快捷。在drupal7中,theme方法也可以使用preprocess方法,就像模板一样。复制和修改template文件相比于对template.php中方法的重写对于主题设计者来说更加适合。

theme方法的命名是把‘theme_'加到钩子名的前面。传进此方法的参数是不会被修改的。在这里,我们不提供注册钩子是需要的默认的参数,但是默认参数对于PHP方法是必须的。

<?php /** * Implements hook_theme(). */ function dashboard_theme() { return array( 'dashboard' => array( 'render element' => 'element', ), // ... ); } ?> 和方法: <?php /** * Returns HTML for the entire dashboard. * * @param $variables * An associative array containing: * - element: A render element containing the properties of the dashboard * region element, #dashboard_region and #children. * * @ingroup themeable */ function theme_dashboard($variables) { extract($variables); drupal_add_css(drupal_get_path('module', 'dashboard') . '/dashboard.css'); return '' . $element['#children'] . ''; } ?> 主题设计者可以建立一个命名为themename_dashboard()的方法来重写这个方法。

动态主题

要想在preprocess方法中明确某一个模板,您需要用通配符来建立动态主题应用。这个过程分为两步.

首先,在hook_theme方法中,您可以定义一个范式。范式可以是简单的一般表达式。^ 作为起始符是需要的,但是$的结束符则不是。要表明范式的动态结点,一般我们用双下划线,这部是必须的,但是是推荐使用的。

第二,当调用theme()方法时,根用一个变量作为第一个参数不同,我们在这里用一个数组作为第一个参数。这个数组跟上面例子中的theme_hook_suggestions相似,但是它是先进先出的,所以第一个看到的将会被应用。

在实际的例子中,module view希望根据名字来把不同的view主题化。在注册过程中,钩子'views_view'将会与范式'views_view__'一起注册。当主题化这个view时,Views模块将调用下面的代码:

$output = theme(array("views_view__$view->name", 'views_view'), $view);

Views模块将为views_view执行一个默认的view。如果有一个主题注册了 'views_view__foo' 方法而且Views模块主题化一个名叫‘foo’的view,一个重写将被执行。与preprocessor方法中的 'theme_hook_suggestions'变量不同,这在theme方法和template文件中都使用。

更多内容请参看preprocess functions.

theme('table') 和 theme('item_list')

Drupal为建立复杂的HTML提供了一些帮助工具。这些是非常游泳的功能,用他们能够简单的生成统一的表格和列表。但是它对主题设计者来说并不容易接触到。因为它把一些本应在表设层的代码放到了逻辑层,只有高级的主体设计者才能使用到它。

这些方法更适用与管理页面。

当我们要建立一个要频繁更改的输出时,最好尽量避免使用这些构造器和用真实的HTML建立表格。论坛的主题方法是一个很好的例子告诉我们如何避免这些并建立统一的HTML 代码。

对使用双下划线为方法命名的建议

对于使用公共theme方法比如theme('item_list') 有一个更大的问题,那就是一个主题也许并不想重写drupal站点中所有的theme_item_list(),它只想重写其中一个在自己模块中的方法。所以,在这里我们用theme('item_list__mymodule__main', $items)来代替theme('item_list'),这样在特定情况下,我们就可以用themename_item_list__mymodule__main()或者themename_item_list__mymodule()来重写item_list()了。

我们可以用另一种方法来代替双下划线,那就是我们可一另取一个所有钩子都可以用到的名字, 在我们的例子中,如下表示:

<?php theme(array('somemodule_itemlist_alternative', 'item_list'), $items); ?>

使我们对代码的改变生效

当一个新的theme方法被添加,我们必须声明主题方法注册,才能看见它们。

参考