Skip to content

Commit 3550e4f

Browse files
author
Nicolò Caruso
committed
Add date/date range form widgets
1 parent 58f44bc commit 3550e4f

File tree

3 files changed

+233
-0
lines changed

3 files changed

+233
-0
lines changed
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
<?php
2+
3+
namespace Drupal\typed_data\Plugin\TypedDataFormWidget;
4+
5+
use Drupal\Core\Datetime\DrupalDateTime;
6+
use Drupal\Core\Form\FormStateInterface;
7+
use Drupal\Core\Form\SubformStateInterface;
8+
use Drupal\Core\TypedData\DataDefinition;
9+
use Drupal\Core\TypedData\DataDefinitionInterface;
10+
use Drupal\Core\TypedData\Type\DateTimeInterface;
11+
use Drupal\Core\TypedData\TypedDataInterface;
12+
use Drupal\typed_data\Form\SubformState;
13+
use Drupal\typed_data\Widget\FormWidgetBase;
14+
use Symfony\Component\Validator\ConstraintViolationListInterface;
15+
16+
/**
17+
* @TypedDataFormWidget(
18+
* id = "datetime_range",
19+
* label = @Translation("DateTime Range"),
20+
* description = @Translation("A datetime range input widget."),
21+
* )
22+
*/
23+
class DateTimeRangeWidget extends FormWidgetBase {
24+
25+
/**
26+
* {@inheritdoc}
27+
*/
28+
public function defaultConfiguration() {
29+
return parent::defaultConfiguration() + [
30+
'label' => NULL,
31+
'description' => NULL,
32+
];
33+
}
34+
35+
/**
36+
* {@inheritdoc}
37+
*/
38+
public function isApplicable(DataDefinitionInterface $definition) {
39+
return is_subclass_of($definition->getClass(), DateTimeInterface::class);
40+
}
41+
42+
/**
43+
* {@inheritdoc}
44+
*/
45+
public function form(TypedDataInterface $data, SubformStateInterface $form_state) {
46+
$value = $data->getValue();
47+
48+
$form = SubformState::getNewSubForm();
49+
$form['#theme_wrappers'][] = 'fieldset';
50+
51+
$form['#title'] = $this->configuration['label'] ?: $data->getDataDefinition()->getLabel();
52+
$form['#description'] = $this->configuration['description'] ?: $data->getDataDefinition()->getDescription();
53+
54+
$form['#element_validate'][] = [$this, 'validateStartEnd'];
55+
56+
$form['value'] = [
57+
'#type' => 'datetime',
58+
'#title' => $this->t('Start date'),
59+
'#default_value' => (isset($value['value'])) ? new DrupalDateTime($value['value']) : '',
60+
];
61+
$form['end_value'] = [
62+
'#type' => 'datetime',
63+
'#title' => $this->t('End date'),
64+
'#default_value' => (isset($value['end_value'])) ? new DrupalDateTime($value['end_value']) : '',
65+
];
66+
return $form;
67+
}
68+
69+
/**
70+
* {@inheritdoc}
71+
*/
72+
public function extractFormValues(TypedDataInterface $data, SubformStateInterface $form_state) {
73+
$value = $form_state->getValue('value');
74+
$end_value = $form_state->getValue('end_value');
75+
if ($value instanceof DrupalDateTime) {
76+
$value = $value->format('Y-m-d H:i:s');
77+
}
78+
if ($end_value instanceof DrupalDateTime) {
79+
$end_value = $end_value->format('Y-m-d H:i:s');
80+
}
81+
$data->setValue([
82+
'value' => $value,
83+
'end_value' => $end_value,
84+
]);
85+
}
86+
87+
/**
88+
* {@inheritdoc}
89+
*/
90+
public function flagViolations(TypedDataInterface $data, ConstraintViolationListInterface $violations, SubformStateInterface $formState) {
91+
foreach ($violations as $violation) {
92+
/** @var ConstraintViolationInterface $violation */
93+
$formState->setErrorByName('value', $violation->getMessage());
94+
}
95+
}
96+
97+
/**
98+
* {@inheritdoc}
99+
*/
100+
public function getConfigurationDefinitions(DataDefinitionInterface $definition) {
101+
return [
102+
'label' => DataDefinition::create('string')
103+
->setLabel($this->t('Label')),
104+
'description' => DataDefinition::create('string')
105+
->setLabel($this->t('Description')),
106+
];
107+
}
108+
109+
/**
110+
* Form #element_validate callback to ensure that the start date <= the end date.
111+
*
112+
* @param array $element
113+
* An associative array containing the properties and children of the
114+
* generic form element.
115+
* @param \Drupal\Core\Form\FormStateInterface $form_state
116+
* The current state of the form.
117+
* @param array $complete_form
118+
* The complete form structure.
119+
*/
120+
public function validateStartEnd(array &$element, FormStateInterface $form_state, array &$complete_form) {
121+
$start_date = $element['value']['#value']['object'];
122+
$end_date = $element['end_value']['#value']['object'];
123+
124+
if ($start_date instanceof DrupalDateTime && $end_date instanceof DrupalDateTime) {
125+
if ($start_date->getTimestamp() !== $end_date->getTimestamp()) {
126+
$interval = $start_date->diff($end_date);
127+
if ($interval->invert === 1) {
128+
$form_state->setError($element, $this->t('The @title end date cannot be before the start date', ['@title' => $element['#title']]));
129+
}
130+
}
131+
}
132+
}
133+
134+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
namespace Drupal\typed_data\Plugin\TypedDataFormWidget;
4+
5+
use Drupal\Core\Datetime\DrupalDateTime;
6+
use Drupal\Core\Form\SubformStateInterface;
7+
use Drupal\Core\TypedData\DataDefinition;
8+
use Drupal\Core\TypedData\DataDefinitionInterface;
9+
use Drupal\Core\TypedData\Type\DateTimeInterface;
10+
use Drupal\Core\TypedData\TypedDataInterface;
11+
use Drupal\typed_data\Form\SubformState;
12+
use Drupal\typed_data\Widget\FormWidgetBase;
13+
use Symfony\Component\Validator\ConstraintViolationListInterface;
14+
15+
/**
16+
* @TypedDataFormWidget(
17+
* id = "datetime",
18+
* label = @Translation("DateTime"),
19+
* description = @Translation("A datetime input widget."),
20+
* )
21+
*/
22+
class DateTimeWidget extends FormWidgetBase {
23+
24+
/**
25+
* {@inheritdoc}
26+
*/
27+
public function defaultConfiguration() {
28+
return parent::defaultConfiguration() + [
29+
'label' => NULL,
30+
'description' => NULL,
31+
];
32+
}
33+
34+
/**
35+
* {@inheritdoc}
36+
*/
37+
public function isApplicable(DataDefinitionInterface $definition) {
38+
return is_subclass_of($definition->getClass(), DateTimeInterface::class);
39+
}
40+
41+
/**
42+
* {@inheritdoc}
43+
*/
44+
public function form(TypedDataInterface $data, SubformStateInterface $form_state) {
45+
$form = SubformState::getNewSubForm();
46+
$form['value'] = [
47+
'#type' => 'datetime',
48+
'#title' => $this->configuration['label'] ?: $data->getDataDefinition()->getLabel(),
49+
'#description' => $this->configuration['description'] ?: $data->getDataDefinition()->getDescription(),
50+
'#default_value' => (!empty($data->getValue())) ? new DrupalDateTime($data->getValue()) : '',
51+
];
52+
return $form;
53+
}
54+
55+
/**
56+
* {@inheritdoc}
57+
*/
58+
public function extractFormValues(TypedDataInterface $data, SubformStateInterface $form_state) {
59+
$value = $form_state->getValue('value');
60+
if ($value instanceof DrupalDateTime) {
61+
$value = $value->format('Y-m-d H:i:s');
62+
}
63+
$data->setValue($value);
64+
}
65+
66+
/**
67+
* {@inheritdoc}
68+
*/
69+
public function flagViolations(TypedDataInterface $data, ConstraintViolationListInterface $violations, SubformStateInterface $formState) {
70+
foreach ($violations as $violation) {
71+
/** @var ConstraintViolationInterface $violation */
72+
$formState->setErrorByName('value', $violation->getMessage());
73+
}
74+
}
75+
76+
/**
77+
* {@inheritdoc}
78+
*/
79+
public function getConfigurationDefinitions(DataDefinitionInterface $definition) {
80+
return [
81+
'label' => DataDefinition::create('string')
82+
->setLabel($this->t('Label')),
83+
'description' => DataDefinition::create('string')
84+
->setLabel($this->t('Description')),
85+
];
86+
}
87+
88+
}

tests/modules/typed_data_widget_test/src/FormWidgetExampleForm.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,21 @@ public function getExampleContextDefinition($widget_id) {
4444
->setDescription('Some example string with max. 8 characters.')
4545
->setDefaultValue('default')
4646
->addConstraint('Length', ['max' => 8]);
47+
4748
case 'select':
4849
return ContextDefinition::create('filter_format')
4950
->setLabel('Filter format')
5051
->setDescription('Some example selection.');
52+
53+
case 'datetime':
54+
return ContextDefinition::create('datetime_iso8601')
55+
->setLabel('Example datetime')
56+
->setDescription('Some example datetime.');
57+
58+
case 'datetime_range':
59+
return ContextDefinition::create('any')
60+
->setLabel('Example datetime range')
61+
->setDescription('Some example datetime range.');
5162
}
5263

5364
}

0 commit comments

Comments
 (0)