Drupal 10:页面加载时打开 Ajax 对话框
注意:这篇文章发布已超过两年,因此其中包含的信息可能已过时。如果您发现了问题,请留下评论,我公司将尽力更正。
2024 年 1 月 7 日 - 阅读时长 17 分钟
Drupal 具备一种快速便捷创建 Ajax 对话框的方法,借助该方法,用户能够在内容里嵌入链接,点击这些链接时会弹出对话框。这是向用户展示部分内容的实用方式,用户无需离开当前页面。
我公司之前曾详细撰写过关于在 Drupal 中创建 Ajax 对话框的文章,并且在有需要时经常会参考那篇文章。
创建 Ajax 对话框最简单的办法是添加类 "use-ajax" 和 data-dialog-type 属性,data-dialog-type 属性的值可以是 dialog、dialog.off_canvas、dialog.off_canvas_top 和 modal 其中之一。使用 "use-ajax" 类可告知 Drupal 这是一个 Ajax 链接,同时拦截点击事件以执行 Ajax 请求。
<a class="use-ajax" data-dialog-type="dialog" href="/node/1">在对话框中打开节点</a>
您还能向 HTML 中注入选项,以此更改对话框中的某些设置。例如,若想将对话框的宽度设定为屏幕尺寸的 70%,那么可以在链接中添加 data-dialog-options 属性。
<a class="use-ajax" data-dialog-options="{"width":"70%"}" data-dialog-type="dialog" href="/node/1">在对话框中打开节点</a>
这两个链接都会在对话框窗口中打开 "/node/1" 页面,而非将用户带到新页面。
近期,我公司在一个网站上遇到这样的需求:若页面被收藏或分享给其他用户,需要重新打开对话框。相关页面存在多个对话框,可在用户无需重新加载页面的情况下向其展示简短内容。默认情况下,打开对话框时不会设置状态,所以我公司需要为对话框系统增添额外功能来实现此特性。
我公司发现添加此状态的最佳方式是在 URL 末尾追加哈希值,如 "#node/123"。这意味着在页面加载时,能够查找这个哈希值并为用户加载 Ajax 对话框。与查询参数不同,Drupal 会忽略哈希值,因此无需担心过滤它们或导致意外的副作用。
一、使用 hook_page_attachments() 钩子添加自定义 JavaScript 库
第一步是使用 hook_page_attachments() 钩子,在每个页面加载时添加一个名为 "mymodule/node_modal" 的自定义 JavaScript 库。这也是 Drupal 开发中常用的一种扩展页面功能的方式。
/**
* 实现 hook_page_attachments() 钩子。
*/
function mymodule_page_attachments(array &$attachments) {
$attachments['#attached']['library'][] = 'mymodule/node_modal';
}
node_modal 库的结构十分简单。我们只需向页面注入一些自定义 JavaScript 代码,并确保页面上也存在 JQuery 和 Drupal Ajax 库。这在 Drupal 模块开发中可以有效地扩展页面的交互功能。
node_modal:
version: 1.0
js:
js/node_modal.js: {}
dependencies:
- core/jquery
- core/drupal.ajax
二、拆解 JavaScript 库代码
这个 JavaScript 库包含一些复杂的代码,接下来我公司将逐步进行拆解。
创建 "Drupal.behaviors" 区域
首先要做的是创建一个 "Drupal.behaviors" 区域,用于包含所有自定义 JavaScript 代码。
(function nodeModalControl($, Drupal) {
'use strict';
Drupal.behaviors.nodeModalControl = {
attach(context, settings) {
// 所有 JavaScript 代码放在这里。
}
};
})(jQuery, Drupal);
遍历元素并添加点击事件
页面首次加载时,我们需要遍历页面上所有包含 data-dialog-type 数据属性的元素,并添加点击事件。这个点击事件会将被点击链接的路径作为哈希值追加到 URL 末尾。
// 查找页面上所有模态链接并为其附加点击事件。
const modalLinks = document.querySelectorAll('[data-dialog-type]');
for (let i = 0; i < modalLinks.length; i++) {
modalLinks[i].addEventListener('click', function openDialogClick(event){
// 点击链接时,将路径作为哈希值添加到 URL 中。
history.pushState('', '', `#${this.pathname}`);
event.preventDefault();
});
}
这样,当用户访问 URL 为 "/node/2" 的页面并点击如下链接时
<a class="use-ajax" data-dialog-options="{"width":"70%"}" data-dialog-type="dialog" href="/node/1">在对话框中打开节点</a>
在对话框打开之前,URL 将变为 "/node/2#/node/1"。这提供了一种方便的方式来存储当前打开的对话框信息,只需共享 URL 即可轻松将其发送给其他用户。
移除对话框关闭后的哈希值
当然,我们并不希望哈希值一直留在 URL 中,所以需要一种机制,在对话框关闭后将其移除。幸运的是,对话框提供了一些事件,我们可以利用这些事件触发自己的代码。当对话框关闭时,会触发 "dialog:afterclose" 事件,我们可以监听该事件并将 URL 重置为原始状态。
// 对话框关闭时,从 URL 中移除哈希值。
$(window).on('dialog:afterclose', (e, dialog,$element) => {
history.pushState("", document.title, window.location.pathname + window.location.search);
});
参数 "window.location.pathname" 属性包含页面的当前路径,"window.location.search" 属性包含页面上可能存在的任何查询参数。通过这样做,我们可以保留可能依赖于页面中查询字符串的任何功能。
检测页面加载时哈希值并触发对话框
最后,我们需要一种方法来检测页面加载时哈希值的存在,并触发对话框显示。
下面这段代码实现了这个功能。在这里,我们检测 URL 中哈希值的存在,提取它,然后使用 Drupal.ajax() 函数触发对话框。我们传递给 Drupal.ajax() 函数的设置本质上与原始对话框链接所使用的选项相同,只是以选项数组的形式呈现。
// 文档加载完成后运行。
once('init-once', context === document ? 'html' : context)
.forEach(function initOnce(doc) {
if (context.hasOwnProperty('location') === false) {
// 如果上下文没有 location 属性,那么这是一个模态窗口,我们不做任何操作。
return;
}
if (context.location.hash !== '') {
// 从 URL 中提取哈希值。
const hash = location.hash.substring(1);
// 创建 Ajax 回调所需的设置。
var ajaxSettings = {
'url': `${hash}`,
'dialogType': 'dialog',
'dialog': {
'width': '70%'
},
};
// 创建 Ajax 回调对象并执行。
var modalAjaxObject = Drupal.ajax(ajaxSettings);
modalAjaxObject.execute();
}
});
关键在于检测当前正在处理的上下文是否有 location 属性。这是因为页面每次加载时都会加载对话框,包括可能触发的任何 Ajax 事件。如果上下文没有 location 属性,那么我们看到的是在 Ajax 对话框中加载的 HTML 片段,可以忽略它。如果没有这个简单的检查,对话框会不断递归触发,直到浏览器内存耗尽。
有了这段 JavaScript 代码,当用户访问包含指向其他内容片段的哈希链接的页面时,就会看到一个 Ajax 对话框。
请注意,这仅适用于节点链接。如果您想允许不同类型的内容或自定义模态链接实现此功能,则需要更改传递给 Drupal.ajax() 函数的 'url' 设置,并创建某种控制器来响应传递给它的 URL。在进行 Drupal 升级到 Drupal11 等操作时,类似的功能处理可能需要重新评估和调整。
这种方法的一个局限性是,可以将任何路径追加到 URL 以强制其加载到页面上。这里仍然使用 Drupal 的权限系统,因此实际上无法以这种方式加载受保护的内容。但是,如果页面加载时出现错误,您可能会看到一条错误消息:“哎呀,出问题了。查看浏览器的开发者控制台以获取更多详细信息。” 跨域资源共享 (CORS) 权限系统也会阻止将任意完整 URL 传递给哈希值,不过会产生相同的错误。
三、使用自定义控制器解决问题
在我公司创建的项目中,通过使用自定义控制器来监听对话框回调解决了这些问题。由于对话框选项是在控制器中设置的,这在一定程度上简化了 Ajax 设置。
// 文档加载完成后运行。
once('init-once', context === document ? 'html' : context)
.forEach(function initOnce(doc) {
if (context.hasOwnProperty('location') === false) {
// 如果上下文没有 location 属性,那么这是一个模态窗口,我们不做任何操作。
return;
}
if (context.location.hash !== '') {
// 从 URL 中提取哈希值。
const hash = location.hash.substring(1);
// 创建 Ajax 回调所需的设置。
var ajaxSettings = {
'url': `/some/ajax/endpoint/${hash}`
};
// 创建 Ajax 回调对象并执行。
var modalAjaxObject = Drupal.ajax(ajaxSettings);
modalAjaxObject.execute();
}
});
通过这种机制,我公司可以控制如何响应 Ajax 请求,从而控制输入的验证以及从请求中返回何种数据。这意味着 Ajax 对话框链接需要指向不同的 URL,并且由于链接总是返回一个 Ajax 对话框,所以不再使用 "data-dialog-type" 属性。相反,链接只需添加一个类即可轻松生成对话框链接。为避免混淆,我公司将链接的创建工作从用户那里抽象出来,这样他们就不必担心创建链接的实现细节。


