Drupal 9:级联Ajax选择表单

借助Drupal 9中内置的Ajax和状态系统,可以很轻松地将表单中的不同选择元素绑定在一起。这意味着任何Drupal表单都可以具有一个select元素,该元素可以将选项显示和更新为同一表单中的另一个select元素。 。使用此系统,您可以创建一个分层系统,其中一个选择将显示并填充另一个选择元素,其中依赖于第一个元素。这非常适合使用户能够深入研究彼此依赖的选项。当用户选择第一选择元素时,第二选择元素将填充数据并显示在屏幕上。

但是,它确实需要首先做好一些准备工作,因此需要一些时间来设置。建立这种系统的方式也有两种,每种方式都有其局限性。我在那里找到了很多有关如何实现一个系统的信息,而没有另一个系统的信息。我以为我会创建一个帖子来展示如何设置每个系统以及可以在哪里使用它。

表格

在跳入ajax组件之前,我们需要一个表单。我能想到的最简单,独立且得到最广泛接受的select元素集是日期选择器。您通常不会将日期显示为一组选择元素(尽管我之前已经看到过),但是其背后的数据很容易理解,不需要使用Drupal的任何其他方面。

这是我们将在此处生成的表格。

在这种状态下,用户可以从技术上选择年,月和日,但是由于表格是静态的,因此天数始终是相同的。如果我们使用Drupal的ajax表单更新机制来实现这一点,我们可以根据选择的月份显示正确的天数。这也允许考虑leap年之类的事情,以便可以动态更改2月的天数。

这是整个表单类。我们将在整个本文的其余部分中添加此代码。提交处理程序只是将输入的数据打印到屏幕上,并且是一种显示正在运行的表单的简单方法。

<?php

 

namespace Drupal\mymodule\Form;

 

use Drupal\Core\Form\FormBase;

use Drupal\Core\Form\FormStateInterface;

 

class SelectAjaxDemo extends FormBase {

 

  /**

   * {@inheritdoc}

   */

  public function getFormId() {

    return 'select_ajax_demo';

  }

 

  /**

   * {@inheritdoc}

   */

  public function buildForm(array $form, FormStateInterface $form_state) {

    $years = range(2019, 2050);

    $years = array_combine($years, $years);

    $year = $form_state->getValue('year');

 

    $form['year'] = [

      '#type' => 'select',

      '#title' => $this->t('Year'),

      '#options' => $years,

      '#empty_option' => $this->t('- Select year -'),

      '#default_value' => $year,

      '#required' => TRUE,

    ];

 

    $months = [1 => 'Jan', 2 => 'Feb', 3 => 'Mar', 4 => 'Apr', 5 => 'May', 6 => 'Jun', 7 => 'Jul', 8 => 'Aug', 9 => 'Sep', 10 => 'Oct', 11 => 'Nov', 12 => 'Dec'];

    $month = $form_state->getValue('month');

 

    $form['month'] = [

      '#type' => 'select',

      '#title' => $this->t('Month'),

      '#options' => $months,

      '#empty_option' => $this->t('- Select month -'),

      '#default_value' => $month,

      '#required' => TRUE,

    ];

 

    $days = range(1, 31);

    $day = $form_state->getValue('day');

 

    $form['day'] = [

      '#type' => 'select',

      '#title' => $this->t('Day'),

      '#options' => $days,

      '#empty_option' => $this->t('- Select day -'),

      '#default_value' => $day,

      '#required' => TRUE,

    ];

 

    $form['submit'] = [

      '#type' => 'submit',

      '#value' => 'Submit',

    ];

 

    return $form;

  }

 

  /**

   * {@inheritdoc}

   */

  public function submitForm(array &$form, FormStateInterface $form_state) {

    $year = $form_state->getValue('year');

    $month = $form_state->getValue('month');

    $day = $form_state->getValue('day');

    $messenger = \Drupal::messenger();

    $messenger->addMessage("Year:" . $year  . " Month:" . $month . " Day:" . $day);

  }

 

}

方法一-单选回调

讨论最简单实现的第一种方法,涉及将每个select元素绑定到表单的ajax回调。

我们需要的第一个ajax实现是在年份选择元素上。将ajax事件属性设置为change意味着,当更改select元素时,将触发ajax回调并更改month元素。该回调 属性,我们将在这个函数调用的功能和包装是将被替换的元素。

$form['year'] = [

  '#type' => 'select',

  '#title' => $this->t('Year'),

  '#options' => $years,

  '#empty_option' => $this->t('- Select year -'),

  '#default_value' => $year,

  '#required' => TRUE,

  '#ajax' => [

    'event' => 'change',

    'callback' => '::yearSelectCallback',

    'wrapper' => 'field-month',

  ],

];

在添加ajax回调之前,我们首先需要向month select元素添加几个项目,以便它可以正确地对回调做出反应。因为我们不希望在选择年份之前显示month select元素,所以我们首先添加一个states属性。进行此设置是为了使当前元素在year select元素中进行设置之前不可见。从技术上讲,这不是ajax回调的一部分,但是通过这种分层形式可以提供更好的用户体验。

为了使ajax回调起作用,我们在month表单元素上设置了前缀后缀,以将该元素包装在div中。这使我们可以通过简单地从ajax回调返回字段来替换整个字段。

$form['month'] = [

  '#type' => 'select',

  '#title' => $this->t('Month'),

  '#options' => $months,

  '#empty_option' => $this->t('- Select month -'),

  '#default_value' => $month,

  '#required' => TRUE,

  '#states' => [

    '!visible' => [

      ':input[name="year"]' => ['value' => ''],

    ],

  ],

  '#prefix' => '<div id="field-month">',

  '#suffix' => '</div>',

];

最后,我们为Years字段设置ajax回调。Years字段回调将返回month字段,并将字段-month div替换为回调中返回的内容。

public function yearSelectCallback(array $form, FormStateInterface $form_state) {

  return $form['month'];

}

这是更改年份选择时调用的事件序列。

  • 选择元素已更改。

  • 状态API被触发,并且由于年份选择包含数据,因此将显示月份选择。

  • Ajax回调被触发。

  • 自举Drupal之后,将form()再次运行整个方法,生成表单元素。

  • yearSelectCallback()调用该 方法,仅返回month表单元素。

  • Drupal渲染返回的元素,使其以HTML格式显示。

  • ajax成功函数(在JavaScript端)用从Drupal返回的HTML的内容替换field-month div。

更进一步,我们现在可以更新“日期”字段以执行相同的操作。

为了做到这一点,我们需要在月份字段中添加一个ajax回调。该回调将由对month select的更改触发,并将monthSelectCallback()在表单类中调用该方法。

$form['month'] = [

  '#type' => 'select',

  '#title' => $this->t('Month'),

  '#options' => $months,

  '#empty_option' => $this->t('- Select month -'),

  '#default_value' => $month,

  '#required' => TRUE,

  '#states' => [

    '!visible' => [

      ':input[name="year"]' => ['value' => ''],

    ],

  ],

  '#ajax' => [

    'event' => 'change',

    'callback' => '::monthSelectCallback',

    'wrapper' => 'field-day',

  ],

  '#prefix' => '<div id="field-month">',

  '#suffix' => '</div>',

];

现在可以用几种不同的方式来更新day字段,而这正是ajax回调的力量开始变得清晰起来的地方。选择月份和年份的元素将包含相同数量的项目,但并非每个月都有相同的天数[需要引用]。这意味着当我们选择年份和月份的组合时,对于years年而言,月份中的天数可能会在2月发生变化。这意味着我们可以动态地获取月份和年份的值,并使用代表日期的正确数量的项目更新选择列表。

使用相同的statesprefixsuffix属性更新day字段,这些属性允许使用states API显示day字段,然后将其替换为ajax回调。

// 从用户输入中提取年和月的值(这已经是表单的一部分,为清楚起见在此重复)。

$year = $form_state->getValue('year');

$month = $form_state->getValue('month');

 

// 计算一个月中的天数。

$number = cal_days_in_month(CAL_GREGORIAN, $month, $year);

$days = range(1, $number);

$day = $form_state->getValue('day');

 

$form['day'] = [

  '#type' => 'select',

  '#title' => $this->t('Day'),

  '#options' => $days,

  '#empty_option' => $this->t('- Select day -'),

  '#default_value' => $day,

  '#required' => TRUE,

  '#states' => [

    '!visible' => [

      ':input[name="month"]' => ['value' => ''],

    ],

  ],

  '#prefix' => '<div id="field-day">',

  '#suffix' => '</div>',

];

该monthSelectCallback()方法非常简单。我们需要做的就是返回day字段。

public function monthSelectCallback(array $form, FormStateInterface $form_state) {

  return $form['day'];

}

这一切都很好。当用户选择的年月份字段被示出,并且当月份选择日字段(含有天的正确的数)被示出。

这里的一个小问题是,如果用户随后选择了不同的年份,则日期字段不会更新。这是因为没有触发几天的Ajax回调,只有在更改月份选择元素时才触发。这是此方法的一个局限性,解决此问题所需的方法略有不同,这使我们进入了方法二。

方法二-ReplaceCommand

由于简单的回调方法具有不更新整个表单的局限性,因此需要进行一些更改。该更改意味着每次更改表单上的选择元素时,所有元素都将使用最新值进行更新。由于包含几个不同的类,因此此方法的实现稍微困难一些。

我们需要做的第一件事是将ajax回调更改为同一方法的所有点。我已经从上述表单设置中删除了一些细节,以减少此处的代码量,因为我所做的只是更改ajax回调属性。

$form['year'] = [

  // ..

  '#ajax' => [

    'event' => 'change',

    'callback' => '::formSelectCallback',

    'wrapper' => 'field-month',

  ],

];

 

$form['month'] = [

  // ..

  '#ajax' => [

    'event' => 'change',

    'callback' => '::formSelectCallback',

    'wrapper' => 'field-day',

  ],

  // ..

];

接下来,我们需要在表单类的顶部添加两个附加的use语句,以将AjaxResponse和ReplaceCommand类引入我们的表单。

use Drupal\Core\Ajax\AjaxResponse;

use Drupal\Core\Ajax\ReplaceCommand;

单个回调函数需要返回一个AjaxResponse对象,而不仅仅是返回form元素。Drupal使用此对象来运行一系列命令,并且我们使用该对象注入几个ReplaceCommand对象来替换表单字段。ReplaceCommand将带有一个HTML元素名称和一个可渲染的Drupal项目(或只是纯HTML),并将用提供的HTML替换该元素。这将运行一堆JavaScript代码,而我们实际上无需编写任何JavaScript。

public function formSelectCallback(array $form, FormStateInterface $form_state) {

  $response = new AjaxResponse();

 

  $response->addCommand(new ReplaceCommand('#field-month', $form['month']));

 

  $response->addCommand(new ReplaceCommand('#field-day', $form['day']));

 

  return $response;

}

最终的结果是,表单将以与简单方法相同的方式起作用,但有一个主要区别。当年份字段同时更新时,方法和日期字段都同时更新,其结果是,如果年份不是a年,并且选择2月作为月份,则天数将自动更新。状态属性仍然隐藏月和日字段,直到它们中包含值为止。

为了说明这一点,下面是每个步骤中表单的外观。

步骤1-无需任何用户交互的表单。

步骤2-选择年份后的表格。

步骤3-选择一个月后的表格。

该方法的缺点是,当更改任何表单字段时,都会呈现更多表单,并通过ajax请求将其发送回去。这可能意味着在大型表单上,由于所有内容都是通过ajax回调重新构建到页面上的,因此可能会变慢。但是,最好只考虑一个回调方法来处理表单,然后立即替换所有元素,因为这样做确实会带来更好的用户体验。当用户填写内容时,您可以更好地控制表单中发生的事情。

以上是 Drupal 9:级联Ajax选择表单 的全部内容, 来源链接: utcz.com/z/343902.html

回到顶部