9.1 创建元素,主题化元素和管理前端库
本课内容
- 组装 examples/theme_example 模块
- 定义一个元素
- 通过渲染数据流处理一个元素
- 学习 Twig 模板
- 学习向元素追加 JavaScript 和 Css
- 学习 Drupal 8 中使用的其他处理模式
本课将演示怎样创建页面的一个组件(元素),包括标记以及 JavaScript、Css 等前端这些东西。我们将展示如何在 PHP 后端定义这个元素,以及它被处理、随着 JavaScript 和 CSS 渲染到页面的整个过程。
定义
元素
Drupal 里,页面的片段被组织成元素,这些元素经常会包含其他的元素。因此我们把这些片段捆绑为大的嵌套结构,修改这些片段,之后渲染为页面或处理为其他的输出形式。元素包括链接(links),按纽(buttons),字段(fields),图片( images),节点(nodes),区块(blocks),页面(pages)等等。所有东西都是某种类型的元素。
渲染数组
PHP 概念中被用于存储元素信息的东西叫做渲染数组(render array),它包含可渲染元素的所有信息。
渲染数组包含定义元素片段信息的元素(例如它的类型)和变量。例如,一个链接有一个类型 link 和使用 title、URL 产生一个 HTML <a> 元素。链接也有很多其他可选的变量,例如 class 和 id 。
之前的课程里我们已经看过控制器产生渲染数组,这些渲染数组通常都是最基本的形式。渲染数组系统把元素呈现为很基本的形式,然后通过可扩展的系统加工它们直至成为 HTML 标记。
在处理过程中,默认值将被添加到渲染数组,定义元素类型的代码以及模块、主题相关逻辑会被执行,最后渲染数组会被交付给产生标记的模板。
我们将构建一个简单元素,看下从渲染数组到形成最终元素的处理流程。所有渲染数组会包含以下属性中的一个:
#markup
#type
#theme
本课大多数时间里,我们将关注 #markup
和 #type
。
Twig 模板
模板是把渲染数组转变为标记的文件。通常模板会包含带有占位符的标记。
<aside>
{{ element.aside }}
</aside>
Twig 是 Drupal 8 中使用的一个基于 PHP 的模板引擎。之前的版本,模板文件使用 PHP 本身转换渲染数组、创建 HTML 标记。之前的版本也偶尔使用 PHP 函数直接输出标记。Drupal 8 只推荐使用模板输出标记。
模板引擎改为 Twig 使模板创建更简单,更安全。
Drupal 之前的版本,因为安全和性能的原因,总被告知不要在模板文件内书写逻辑。Twig 限制比较多,它提供的结构有助于缓解这种担忧,语法相对于其他前端系统比较简单,前端开发者更容易上手。
JavaScript / 样式表/ 前端库
Drupal 8 里,我们捆绑与元素相关的所有 JavaScript 和 Css 为一个前端库,附加(使用 #attached)这个库到渲染数组。这也是 Drupal 7 中被推荐的方式,但很少被使用。Drupal 7 中也支持运行中追加 JavaScript 和 CSS。
除了一些小的变化,JavaScript 和 CSS 本身与在之前的 Drupal 没太大区别。
定义我们的元素
本课我们将创建一个叫做 My Element 的元素,它包含一个链接、一个描述和一个随机数。这个元素也会加载相关的 CSS 和 JavaScript。
<div>
<div>Random Number: 42</div>
<p>Here is a description</p>
<a href="http://www.drupal.org">Drupal.org</a>
</div>
要显示这个元素,我们将创建一个包含这个元素的页面。
安装我们的模块
我们快速创建模块,提供显示这个新元素的例子页面。
theme_example.info.yml
name: Theme Example
type: module
description: Example showing how to add a custom element and add JavaScript and CSS as a library
core: 8.x
package: Examples
theme_example.routing.yml
theme_example.simple:
path: 'examples/theme-example/simple'
defaults:
_controller: '\Drupal\theme_example\Controller\ThemeExampleController::simple'
requirements:
_access: 'TRUE'
src/Controller/ThemeExampleController.php(见辅助内容区)
<?php
/**
* @file
* Contains \Drupal\theme_example\Controller\ThemeExampleController.
*/
namespace Drupal\theme_example\Controller;
use Drupal\Core\Controller\ControllerBase;
/**
* Controller routines for theme example routes.
*/
class ThemeExampleController extends ControllerBase {
public function simple() {
return [
'example one' => [
'#markup' => '<div>Markup Example</div>',
],
'example two' => [
'#type' => 'my_element',
'#label' => $this->t('Example Label'),
'#description' => $this->t('This is the description text.'),
],
];
}
}
创建渲染数组
在这个控制器(ThemeExampleController.php
)里,我们创建了三个元素渲染数组。第一个是容器,包含 example one 和 example two 两个渲染数组。example one 是最基本类型的渲染数组。最简单形式的渲染数组包含一个键 #markup
以及标记输出的一个关联值。
$output = [
'#markup' => '<div>Markup Example</div>',
];
像 example two 这样比较复杂一些的渲染数组包含一组以 # 为前缀的变量。
第二个渲染数组最终将成为我们的元素标记。被 #type my_element
定义,包含 label 和 description 变量。此时它不包含链接或随机数孩子元素。这些我们将在后面追加。
$output = [
'#type' => 'my_element',
'#label' => $this->t('Example Label'),
'#description' => $this->t('This is the description text.'),
];
用渲染元素插件定义一个元素
我们已经看过元素被包含在控制器中的情况,我们需要定义元素是什么,以及它将如何被处理。我们将使用 #type ,使用一个渲染元素插件。像其他插件一样我们在 src/Element/
里创建一个新类,继承基类 RenderElement
。这种形式允许我们把元素相关的所有信息和逻辑存储在一个文件内。
注解
这个元素的注解定义元素名,这样系统才可以找到它。我们追加注解 @RenderElement
,并设置为我们的 #type 名。
getInfo()
getInfo() 函数定义了我们的元素是怎样的结构。我们提供了默认值,也用 #type 相同的名字追加了一个 #theme 变量。稍后我们将在模块的 hook_theme()
内定义这个 #theme 。
大多数类似的配置已经移到 YML 文件,但主题系统在 Drupal 8 内还是有些复杂。
#pre_render 函数
我们的组件包含一个 #pre_render 函数,用于处理基本的渲染数组以及为模板创建必要的片断。Drupal 7 中,预处理函数做这些事情。预处理函数仍然存在,只是对模块和主题有意义,不再定义元素。在 pre_render
函数里,我们使用变量创建了一个渲染数组,包含几个孩子和一个变量 #random_number
。
src/Element/MyElement.php (见辅助内容区)
hook_theme()
在 hook_theme() 里,指定使用渲染元素模式,并提供了一个 Twig 变量 element
。使用最广泛的就是渲染元素模式和 Twig 模板。在之前的 Drupal 版本,大多数元素信息被定义在 hook_theme() 内,但在 Drupal 8 里,这些逻辑现在在插件内。
theme_example.module
<?php
/**
* Implements hook_theme().
*/
function theme_example_theme() {
$items = [
'my_element' => [
'render element' => 'element',
],
];
return $items;
}
<?php
/**
* @file
* Contains \Drupal\theme_example\Element\MyElement.
*/
namespace Drupal\theme_example\Element;
use Drupal\Core\Render\Element\RenderElement;
use Drupal\Core\Url;
/**
* Provides an example element.
*
* @RenderElement("my_element")
*/
class MyElement extends RenderElement {
/**
* {@inheritdoc}
*/
public function getInfo() {
$class = get_class($this);
return [
'#theme' => 'my_element',
'#label' => 'Default Label',
'#description' => 'Default Description',
'#pre_render' => [
[$class, 'preRenderMyElement'],
],
];
}
/**
* Prepare the render array for the template.
*/
public static function preRenderMyElement($element) {
// Create a link render array using our #label.
$element['link'] = [
'#type' => 'link',
'#title' => $element['#label'],
'#url' => Url::fromUri('http://www.drupal.org'),
];
// Create a description render array using #description.
$element['description'] = [
'#markup' => $element['#description']
];
$element['pre_render_addition'] = [
'#markup' => 'Additional text.'
];
// Create a variable.
$element['#random_number'] = rand(0,100);
return $element;
}