Drupal 11:使用 HTMX 的节点显示模式预览表单
这是探讨 Drupal 中 HTMX 的系列文章的第五部分,相关文章列表会在本文末尾列出。
我在思考如何在 Drupal 中演示 HTMX 时,实现了无限滚动、标签式界面和级联选择表单等功能,基本是在 Drupal 模块开发中重现了我在非 Drupal 的 HTMX 里做过的事。
后来我有了个想法,创建一个对 Drupal 开发者日常工作可能真正有用的东西,也就是以不同视图模式显示节点。
本文将探讨创建一个简单表单,允许用户输入节点 ID 和视图模式,查看以该视图模式渲染的节点。
本文包含的所有代码都能找到,这里会详细介绍代码功能以及它为生成内容所做的操作。
和其他关于 HTMX 的文章一样,我从定义路由开始。
一、路由
这里所需的路由只需将路径 /htmx-examples/display-mode-preview 指向我们的表单类。
drupal_htmx_examples_display_mode_preview_form:
path: "/htmx-examples/display-mode-preview"
defaults:
_form: '\Drupal\drupal_htmx_examples\Form\DisplayModePreviewForm'
_title: "HTMX 显示模式预览表单"
requirements:
_permission: "access content"
这个路由没什么特别的,就是个常规的表单路由。接下来为这个路由创建表单。
二、表单
表单类有几个注入的依赖项,如下:
entity_type.manager- 用于从数据库中加载节点,并加载视图构建器以渲染节点。entity_display.repository- 用于加载节点可用的视图模式列表。
以下是表单的骨架,buildForm() 方法稍后介绍。
entityTypeManager = $container->get('entity_type.manager');
$instance->entityDisplayRepository = $container->get('entity_display.repository');
return $instance;
}
public function getFormId() {
return 'display_mode_preview_form';
}
public function submitForm(array &$form, FormStateInterface $form_state): void {
// 不做任何操作。
}
}
buildForm() 方法较复杂,本质上要完成以下操作:
- 接受表单中的用户输入(未设置输入时,设置合理默认值)。
- 将节点字段设为数字字段。
- 获取实体类型的可用视图模式列表,用该列表创建选择元素,供用户选择视图模式。
- 用户选择节点后,从数据库中加载该节点。
- 用实体类型的视图构建器为所选视图模式(默认为“full”)中的节点生成渲染数组。
- 创建 ID 为“node-preview-output”的 HTML div 元素,将渲染数组作为子元素传入,让渲染器在 div 元素内渲染节点渲染数组。
以下是表单操作的代码。
public function buildForm(array $form, FormStateInterface $form_state) {
// 获取用户输入(如果设置了),并设置一些合理的默认值。
$nid = $form_state->getValue('node_id', '');
$viewMode = $form_state->getValue('view_mode', 'full');
// 节点 ID 输入字段。
$form['node_id'] = [
'#type' => 'number',
'#title' => $this->t('节点 ID'),
'#default_value' => $nid,
];
// 加载节点的视图模式。
$viewModes = $this->entityDisplayRepository->getViewModes('node');
$viewModesSelection = [];
foreach ($viewModes as $id => $mode) {
$viewModesSelection[$id] = $mode['label'];
}
// 创建视图模式选择字段。
$form['view_mode'] = [
'#title' => $this->t('视图模式'),
'#type' => 'select',
'#empty_option' => $this->t('- 选择 -'),
'#options' => $viewModesSelection,
'#default_value' => $viewMode,
];
// 为节点输出创建一个占位符。
$form['output'] = [
'#markup' => '',
];
if ($nid === '') {
// 如果未设置节点 ID,则在此返回。
return $form;
}
// 使用输入的 ID 加载节点。
$nodeStorage = $this->entityTypeManager->getStorage('node');
$node = $nodeStorage->load($nid);
if (!$node) {
// 如果没有找到节点(用户可能输入了一个不存在的节点 ID),则在此返回一个简单的消息。
$form['output'] = [
'#type' => 'html_tag',
'#tag' => 'div',
'#value' => $this->t('未找到节点'),
'#attributes' => [
'id' => 'node-preview-output',
],
];
return $form;
}
// 如果有节点,则使用所选视图模式渲染它,并将其注入到输出表单元素的 'children' 元素中。
// 这样,在渲染表单元素时,节点也会被渲染。
$viewBuilder = $this->entityTypeManager->getViewBuilder('node');
$renderArray = $viewBuilder->view($node, $viewMode);
$form['output'] = [
'#type' => 'html_tag',
'#tag' => 'div',
'#attributes' => [
'id' => 'node-preview-output',
],
'children' => $renderArray,
];
return $form;
}
ID 为 node-preview-output 的 div 元素在表单逻辑中添加了三次,但任何时候只渲染其中一个。这个元素用于在用户与表单交互时,将表单内容注入页面。默认页面加载时,该元素只是空白占位符。
若没有 HTMX,这个表单什么也做不了,submitForm() 方法无需做任何事,因为所有操作都由 HTMX 处理,甚至没有提交处理程序。接下来给表单添加 HTMX 功能。
三、添加 HTMX
为添加交互性,需将 HTMX 添加到 node_id 和 view_mode 字段中。
从 node_id 字段开始,为该字段添加以下 HTMX 设置。
$form['node_id'] = [
'#type' => 'number',
'#title' => $this->t('节点 ID'),
'#default_value' => $nid,
];
(new Htmx())
->post()
->trigger('input delay:0.5s')
->target('#node-preview-output')
// 选择是必需的,以便从我们的响应中挑选出正确的元素,响应将包含整个表单。
->select('#node-preview-output')
->swap('outerHTML')
->applyTo($form['node_id']);
这会为表单添加以下 HTMX 属性。
data-hx-post- 向表单发送 POST 请求。因未给该方法加参数,会自动使用当前路由。- data-tx-trigger - 告诉 HTMX 用户在字段输入内容时向表单发请求,发请求前有 0.5 秒延迟。
data-hx-target- 告知 HTMX 将响应提取的元素放哪里。这里将响应目标设为 ID 为 node-preview-output 的 div 元素。data-hx-select- 在 Drupal 处理 HTMX 表单时必不可少。之前文章提到,发 HTMX 请求会从服务器得到整个表单,所以要告诉 HTMX 从响应中挑出所需元素。这里用选择器字符串 "#node-preview-output",让 HTMX 找含渲染节点的该 ID 元素。data-hs-swap- 将交换策略设为outerHTML,让 HTMX 用响应所选值完全替换目标元素。
view_mode 字段用以下 HTMX 设置。
$form['view_mode'] = [
'#title' => $this->t('视图模式'),
'#type' => 'select',
'#empty_option' => $this->t('- 选择 -'),
'#options' => $viewModesSelection,
'#default_value' => $viewMode,
];
(new Htmx())
->post()
->target('#node-preview-output')
// 选择是必需的,以便从我们的响应中挑选出正确的元素,响应将包含整个表单。
->select('#node-preview-output')
->swap('outerHTML')
->applyTo($form['view_mode']);
这会为该字段添加类似的 HTMX 属性集合。这里没添加触发属性,因为该元素是选择字段,值更改时有触发机制。
本质上是让 HTMX 监听这两个字段,向表单发请求,将 ID 为 node-preview-output 的 div 元素替换为渲染响应中相同元素的内容。
四、HTMX 工作流程
HTMX 元素的工作方式如下。
为便于阅读和理解,简化并删除了不少标记。
这里 HTMX 工作流程很简单,从表单的三个元素(两个表单元素和一个 div 元素)开始。
用户与表单交互,如输入节点 ID 42 和视图模式“摘要”,HTMX 会向服务器发请求,用该信息重新渲染表单。返回的响应含整个页面及新构建表单,从中提取 node-preview-output 元素,替换页面上相同 ID 的元素。
由于 HTMX 在 Drupal 中的工作方式,div 元素中的节点会用正确的 CSS 完全渲染。核心 HTMX 库中有个名为 htmx-assets.js 的文件,用于从响应中提取 CSS 和 JavaScript 文件并添加到当前页面头部。意味着即使节点用原始页面请求中不存在的单个目录组件渲染,整个节点也能正确渲染。
以下是该表单使用时的演示,加载了节点预览。
在 Drupal 中使用 HTMX 时,掌握表单处理中的选择和交换组合很重要。这里从我公司之前文章中复制在 Drupal 中构建 HTMX 表单时必须记住的两点。
- Drupal 的响应总是包含整个表单,所以必须从表单响应中挑选出想要的元素。不这样做会看到表单出现在表单内部,比较混乱。
- 表单必须有一致的状态。即不能随意向表单添加元素就期望它正常工作。无论有无 HTMX 请求,表单状态都要以相同方式重新构建表单。从表单提交流程看,表单先构建再提交,若元素在构建步骤不存在,就不会成为提交处理程序中提交值的一部分。
这个表单的优点是,渲染后的表单也适用于单个目录组件。我公司已在几个网站上测试,它通过 HTMX 回调在任何视图模式下渲染任何节点都表现良好,在 Drupal 开发和 Drupal 升级过程中都能发挥不错的作用。对于 Drupal11 版本,同样具有实用性。
实际上,这几乎能成为一个独立模块。若你认为它足够有用,可成为完整模块,请联系我公司,我公司会创建这个项目。


