Drupal 8:向现有行为添加事件
我最近需要向密码策略模块添加功能,所以我想我会在博客文章中概述我采取的步骤。密码策略模块用于对Drupal站点上的用户实施更严格的密码策略。这意味着当用户创建或更改他们的密码时,他们必须遵守某些规则,例如密码长度,或者是否包含大写和小写字符。有一组规则可供选择,站点管理员可以完全自定义它们。这是一个很好的模块,你应该检查一下。
我最近在一个使用这个模块一段时间的网站上工作。我正在考虑改善整个站点的用户体验,其中突出的一件事是密码策略模块仅在您离开密码字段后才会触发。我决定构建一个小模块,它会向密码字段添加实时检查,该字段会随着用户填写密码而更新。由于创建密码策略模块的方式,我需要覆盖钩子并用我自己的一些来扩充现有的JavaScript。
这篇文章是我研究如何在非常特定的区域覆盖密码策略模块以添加此实时检查功能的。它还展示了如何使用几行代码来扩充现有的JavaScript事件。
第一步是删除密码策略模块添加到系统的钩子。该模块有一个hook_element_info_alter()钩子,用于将表单处理功能添加到密码确认字段。当我们希望改变这个方法时,我们需要从Drupal中删除这个钩子,以便我们可以添加自己的版本。可以使用钩子从Drupal系统中删除已实现的hook_module_implements_alter()钩子。我们只需要找到相关的钩子并确保它不存在。
/** * Implements hook_module_implements_alter(). */ function my_module_module_implements_alter(&$implementations, $hook) { if ($hook == 'element_info_alter' && isset($implementations['my_module'])) { if (isset($implementations['password_policy'])) { //如果设置了password_policy模块,则删除其 //element_info_alter钩子,因为我们将实现我们自己的钩子。 unset($implementations['password_policy']); } } }
随着hook_module_implements_alter()勾走了,我们需要建立我们自己的版本。这是通过复制此钩子的密码策略版本并更改一些名称来完成的。密码确认上的#process元素允许我们在元素本身的处理过程中很晚才更改元素,这意味着我们可以立即影响每个密码确认元素。此处使用process元素是我们需要完全覆盖此方法的原因,因为无法拦截上游。
/** * Implements hook_element_info_alter(). */ function my_module_element_info_alter(array &$types) { if (isset($types['password_confirm'])) { $types['password_confirm']['#process'][] = 'my_module_password_policy_check_constraints_password_confirm_process'; } }
这种方法的替代方法是使用hook_module_implements_alter()钩子注入我们的版本,hook_element_info_alter()以便在密码策略模块版本之后执行。如果您有兴趣,我在之前的文章中写过关于改变钩子重量的文章。
有了这个,我们就可以创建我们的流程功能。该函数存在于PasswordPolicy模块中,用于将所需的ajax选项注入该字段。密码策略模块向ajax调用添加了一个disable-refocus参数,这对我们在这里尝试实现的目标造成了问题。此disable-refocus参数可防止在触发ajax事件后重新聚焦该字段。这被删除,并添加了一个新的库到元素中,我们可以注入JavaScript来控制自动更新。从根本上说,我们保留了模块提供的现有功能和事件,但添加了我们自己的库并调整了现有设置。
请记住,密码确认元素实际上作为两个字段存在。密码字段(称为pass1)和密码确认字段(称为pass2),因此此操作将应用于第一个密码字段。
function my_module_password_policy_check_constraints_password_confirm_process(array $element, FormStateInterface $form_state, array $form) { $form_object = $form_state->getFormObject(); if (method_exists($form_object, 'getEntity') && $form_object->getEntity() instanceof UserInterface) { if (_password_policy_show_policy()) { $element['pass1']['#ajax'] = [ 'event' => 'change', 'callback' => '_password_policy_check_constraints', 'method' => 'replace', 'wrapper' => 'password-policy-status', ]; $element['pass1']['#attached']['library'][] = 'my_module/password_event'; $element['pass1']['#attributes']['class'][] = 'passwordtimer'; } } return $element; }
由于我们向模块添加了一个新的JavaScript库,因此我们需要为它创建一个my_module.libraries.yml文件。
password_event: js: js/password-event.js: {} dependencies: - core/jquery - core/drupal - core/drupal.ajax
有了这个,我们可以创建js/password-event.js文件。此JavaScript文件用于包装密码策略模块放置的现有更改事件。实际上,是Drupal根据密码策略模块中的选项创建此JavaScript,而不是模块本身将代码注入站点。添加到现有事件使用了在上一篇关于检查和重用jQuery事件的文章中学到的一些经验教训。我们在这里所做的是从密码字段中提取现有的更改事件并将其包装在一个事件中,该事件检测用户是否在同一字段上按下了一个具有一秒偏移量的键。这意味着如果用户停止输入,那么我们会触发原始更改事件并向用户显示他们的密码策略更新。
(function ($, Drupal) { 'use strict'; Drupal.behaviors.bform = { attach : function(context, settings) { //加载现有的更改事件。 var events = $._data($(".passwordtimer")[0], "events"); //创建函数来触发加载的事件。 function doneTyping(event) { $.each(events.change, function() { this.handler(event); }); } var typingTimer; var doneTypingInterval = 1000; //当用户按下一个键时触发一个事件。 $('.passwordtimer').keyup(function(event) { clearTimeout(typingTimer); typingTimer = setTimeout(doneTyping, doneTypingInterval, event); return false; }); } }; })(jQuery, Drupal);
我们在触发此事件之前等待一秒钟的原因是因为否则当用户输入字段时屏幕会跳来跳去。通过将更新偏移一秒,我们可以确保用户打字的时间足够长,无法查看更新。
有了所有这些,密码策略模块现在具有实时密码检查功能,但在用户交换字段后仍具有原始检查功能。
我会建议总是做这种事情吗?现在回想起来,几周后,可能不会。尽管可以在Drupal中覆盖这样的功能,但它确实会产生一些技术债务。你这样做的次数越多,你打开代码的错误就越多。如果底层模块更改其结构,那么您的代码将无法运行,您将被迫重新访问它。在某一点上,通过针对模块的简单补丁可以更好地解决一系列复杂的钩子覆盖,回想起来,这可能是我应该在这里做的。