在 Drupal 中驯服 JavaScript(包括常见问题解答)
已发表: 2023-02-07交互式 Web 体验为用户提供了更具吸引力和愉悦的体验。 它可以提高用户满意度和对网站的积极看法。 例如,提供即时反馈和验证的表单,而不是让用户等待页面刷新,可以显着改善用户体验。
JavaScript 通过提供在 Drupal 网站前端为用户创建动态和交互式体验的方法,在 Drupal 中发挥着重要作用。 它使开发人员能够修改页面上某些元素的行为,例如表单、链接或任何其他 DOM 元素,而无需刷新整个页面。 Drupal 行为是在页面上发生特定事件时执行的 JavaScript 函数。 行为使开发人员可以轻松维护和升级站点,因为他们不需要更改任何底层 HTML 代码。 在文章中找到您想了解的有关 Drupal 行为的所有信息。
什么是 Drupal 行为?
Drupal.behaviors是 Drupal 中 Javascript 结构中的一个对象,它允许我们附加要在应用程序执行期间的特定时间执行的函数。 它在 DOM 完全加载时调用,但这些行为可以再次调用。 Drupal 的官方 JavaScript 文档建议模块应该通过将逻辑附加到Drupal.behaviors来实现 JavaScript。
为什么我们需要 Drupal 行为?
行为的优点是它们会自动重新应用于通过 AJAX 加载的任何内容。 可以随时使用表示对 DOM 的新添加或更改的上下文调用它们。 这比代码只运行一次的$(document).ready()或文档 DOMContentLoaded更好。
什么时候 Drupal 行为是不需要的?
Drupal 行为并不总是在 Drupal 中编写 Javascript 的完美解决方案。 在某些情况下,如下所述,根本不需要 Drupal 行为!
- 当我们需要执行一些不影响 DOM 的代码时。 例如。 初始化外部脚本,如 Google Analytics
- 当某些 JS 操作需要在 DOM 上执行一次时,知道该元素将在页面加载时可用(这种情况与使用 Once 不同)。
什么时候调用 Drupal 行为?
- 在将管理叠加层加载到页面中之后。
- AJAX 表单 API 提交表单后。
- 当 AJAX 请求返回修改 HTML 的命令时,例如ajax_command_replace() 。
其他时候调用 Drupal 行为
- CTools 在加载模式后调用它。
- Media 在加载媒体浏览器后调用它。
- Panels 在就地编辑完成后调用它。
- Views 在加载使用 AJAX 的新页面后调用它。
- Views Load More 在加载下一个项目块后调用它。
- 来自自定义模块的 JavaScript 在添加或更改页面的某些部分时可能会调用Drupal.attachBehaviors() 。
编写没有 Drupal 行为的代码
在这段代码中,我们向.views-row类添加了一个点击事件监听器,它计算我们点击该行的次数。 但是它只在初始页面加载期间添加到 DOM 中的元素一次。 单击加载更多并加载更多项目后,点击侦听器不适用于新加载的项目。
// No Drupal Behaviors (function () { let header = document.querySelector(".food-list-header"); if (header) { let greatFoodSpan = document.createElement("span"); greatFoodSpan.textContent = "Get ready for great food!!!!!!"; header.append(greatFoodSpan); } // Add the event listener for each click on the food let foods = document.querySelectorAll(".views-row"); foods.forEach((food) => { food.addEventListener("click", () => { let foodCounter = food.querySelector(".food-click-counter"); let timesClicked = parseInt(foodCounter.textContent.trim()); foodCounter.textContent = ++timesClicked; }); }); })();
我们如何使用 Drupal 行为?
答:使用attach方法
要记住的事情:
- 新对象至少需要有一个附加方法。
- 任何时候调用 Drupal.attachBehaviors 时,它都会遍历所有行为对象并调用它们各自的附加方法。
将 Drupal 行为添加到我们的代码中
添加 Drupal Behaviors 后,代码看起来像这样。
(function (Drupal) { Drupal.behaviors.exampleBehaviour1 = { attach: (context, settings) => { // Add a delicious text to the top of the document let header = document.querySelector(".food-list-header"); // jQuery Equivalent // $(".food-list-header"); if (header) { let greatFoodSpan = document.createElement("span"); greatFoodSpan.textContent = "Get ready for great food!!!!!!"; header.append(greatFoodSpan); } // Add the event listener for each click on the food let foods = document.querySelectorAll(".views-row"); foods.forEach((food) => { food.addEventListener("click", () => { let foodCounter = food.querySelector(".food-click-counter"); let timesClicked = parseInt(foodCounter.textContent.trim()); foodCounter.textContent = ++timesClicked; }); }); }, }; })(Drupal);
但是当我们点击Load More 时,顶部会出现一些奇怪的东西:
这是因为 Drupal 行为被调用了很多次,随后我们得到了一些意想不到的行为。
“Drupal 上下文”中的上下文是什么?
- 当为所有行为调用附加方法时,Drupal 会传递一个上下文参数。
- 传递的上下文参数通常可以更好地了解正在处理的 DOM 元素。
- 在初始页面加载期间,这将是完整的 HTML 文档; 在随后的调用中,这将只是被添加到页面或被修改的元素。
如何添加上下文?
前面的问题可以通过使用 Drupal Behaviors 提供的上下文参数来解决。 在这种情况下,第一次加载页面时,我们将整个 HTML 文档作为上下文,这就是我们附加标题的时候。 对于进一步的操作,它将是受 Drupal 行为影响的代码部分,因此该部分代码是安全控制的。
(function (Drupal) { Drupal.behaviors.exampleBehaviour2 = { attach: (context, settings) => { // Add a delicious text to the top of the document. // The context parameter now can be used for adding // certain functionality which removes unwanted repeatability let header = context.querySelector(".food-list-header"); // jQuery Equivalent // $(".food-list-header", context); if (header) { let greatFoodSpan = document.createElement("span"); greatFoodSpan.textContent = "Get ready for great food!!!!!!"; header.append(greatFoodSpan); } // Add the event listener for each click on the food let foods = context.querySelectorAll(".views-row"); foods.forEach((food) => { food.addEventListener("click", () => { let foodCounter = food.querySelector(".food-click-counter"); let timesClicked = parseInt(foodCounter.textContent.trim()); foodCounter.textContent = ++timesClicked; }); }); }, }; })(Drupal);
当我们点击Load More时,再次出现一些奇怪的行为。 最初装载的食品工作正常。 但是在点击 Load More 之后,新项目获得点击监听器并正常工作。 但是最初加载的项目会再次附加监听器,点击它们会不止一次调用点击事件!
Drupal Behaviors 什么时候开始行为不端?
- 在不使用 Once 和 Context 的情况下在 Drupal 行为中编写所有事件侦听器。
- 在 Drupal 行为中声明不需要的函数,这会导致每次调用附加方法时都重新声明函数。
“曾经”拯救
- Once 通过在代码执行后在 DOM 元素中添加 data-once 属性来确保仅处理一次。
- 如果再次调用该行为,则跳过具有 data-once 属性的元素以继续执行。
- Once 是jQuery.once的现代实现(这是摆脱 jQuery 的一种努力)
- 一次,结合上下文,完美地控制整个功能,如我们所需要的那样。
添加一次以修复我们代码中的事件侦听器
(function (Drupal, once) { Drupal.behaviors.exampleBehaviour3 = { attach: (context, settings) => { once("food-header-initialized", ".food-list-header", context).forEach( (header) => { let greatFoodSpan = document.createElement("span"); greatFoodSpan.textContent = "Get ready for great food!!!!!!"; header.append(greatFoodSpan); } ); // jQuery Equivalent // $(".food-list-header", context).once("food-header-initialized", function (header) { // // }); // Add the event listener for each click on the food once("food-initialized", ".views-row", context).forEach((food) => { food.addEventListener("click", () => { let foodCounter = food.querySelector(".food-click-counter"); let timesClicked = parseInt(foodCounter.textContent.trim()); foodCounter.textContent = ++timesClicked; }); }); }, }; })(Drupal, once);
现在一切都按预期工作。 我们为附加了事件侦听器的元素获得了一次数据属性,并且新加载的元素和先前加载的元素正常运行。
需要分离方法
Detach 方法就像一个反英雄(不是邪恶的),删除我们在 attach 方法中所做的一切。 每当从 DOM 中删除内容时,都会调用 detach 方法中的任何代码。 这有助于我们清理我们的应用程序。 例如,Detach 方法使我们能够删除不需要的事件侦听器,这些侦听器会像连续轮询情况一样消耗资源。
分离的例子
假设我们有一个要填写的 ajax 表单,并且我们正在使用一个计时器来显示经过的时间。 我们使用 setTimeOut 来管理定时器。 我们将这个计时器记录在控制台中以进行监控。
(function (Drupal, once) { let counter = 0; Drupal.behaviors.exampleBehaviour4 = { attach: (context, settings) => { once("timer-initalized", ".contact-timer", context).forEach((ele) => { const timer = context.querySelector(".contact-timer-sec"); timer.textContent = counter; // Set the timer for user to see the time elapsed setInterval(() => { console.log("This is logging"); const timer = document.querySelector(".contact-timer-sec"); timer.textContent = ++counter; }, 1000); }); }, }; })(Drupal, once);
在提交表单时,DOM 上的计时器被删除,但控制台开始抛出错误。 这是因为 setTimeOut 所作用的元素已从 DOM 中移除:
为了避免这种情况,我们可以像这样使用 detach 方法:
(function (Drupal, once) { let counter = 0; let intervalStopper; Drupal.behaviors.exampleBehaviour4 = { attach: (context, settings) => { // Set the timer for user to see the time elapsed once("timer-initialized", ".contact-timer", context).forEach((ele) => { const timer = context.querySelector(".contact-timer-sec"); timer.textContent = counter; intervalStopper = setInterval(() => { const timer = document.querySelector(".contact-timer-sec"); timer.textContent = ++counter; console.log("This is logging"); }, 1000); }); }, // Clear the timer on confirmation detach: (context, settings, trigger) => { const timer = context.querySelector(".contact-timer-sec"); if (trigger == "unload" && timer) { clearInterval(intervalStopper); } }, }; })(Drupal, once);
这会删除卸载时的计时器,并且从记录器中可以看出,错误不会发生。
立即调用函数表达式 (IIFE) - JS 的包装器
我们一直在使用 IIFE 来编写我们的 Drupal 代码。 最初的左括号定义了一个匿名函数,它有助于防止函数的作用域污染整个应用程序的全局作用域。 您可以通过将参数作为参数包含在函数定义的末尾来将参数传递给匿名函数。
这也有助于我们为参数命名空间,但我们希望使用它们。
例子:
// Function name crisis!!!! // The function is vulnearble to // be replaced by some other function function someFunction() { // Some code for this function } (function (Drupal) { // Function name crisis averted! function someFunction() { // Some code for this other function } Drupal.behaviors.exampleBehaviour6 = { attach: (context, settings) => { someFunction(); }, }; })(Drupal);
最后的想法
实施 Drupal 行为允许动态交互、简化用户交互、改进用户反馈、高效开发和整体增强网站的用户体验。 Drupal.behaviors 是灵活的和模块化的,因为它们可以在页面上多次执行,可以覆盖和扩展现有行为,并且可以自动重新应用于通过 Ajax 加载的任何内容。
正在寻找 Drupal 开发机构来帮助您构建交互式 Web 体验,充分利用 Drupal? 我们很想谈谈!