3 namespace Drupal\entityqueue\Plugin\Field\FieldWidget;
5 use Drupal\Component\Utility\Html;
6 use Drupal\Component\Utility\NestedArray;
7 use Drupal\Core\Entity\Element\EntityAutocomplete;
8 use Drupal\Core\Field\FieldItemListInterface;
9 use Drupal\Core\Field\FieldStorageDefinitionInterface;
10 use Drupal\Core\Field\Plugin\Field\FieldWidget\EntityReferenceAutocompleteWidget;
11 use Drupal\Core\Form\FormStateInterface;
14 * Plugin implementation of the 'entityqueue_dragtable' widget.
17 * id = "entityqueue_dragtable",
18 * label = @Translation("Autocomplete (draggable table) - Experimental"),
19 * description = @Translation("An autocomplete text field with a draggable table."),
25 class EntityqueueDragtableWidget extends EntityReferenceAutocompleteWidget {
28 * The unique HTML ID of the widget's wrapping element.
37 public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
38 $field_name = $this->fieldDefinition->getName();
39 $parents = $form['#parents'];
40 $referenced_entities = $items->referencedEntities();
42 if (isset($referenced_entities[$delta])) {
43 $entity_label = EntityAutocomplete::getEntityLabels([$referenced_entities[$delta]]);
44 $id_prefix = implode('-', array_merge($parents, [$field_name, $delta]));
47 '#type' => 'container',
48 '#attributes' => ['class' => ['form--inline']],
51 '#markup' => $entity_label,
52 '#default_value' => !$referenced_entities[$delta]->isNew() ? $referenced_entities[$delta]->id() : NULL,
56 '#default_value' => $referenced_entities[$delta],
60 '#name' => strtr($id_prefix, '-', '_') . '_remove',
61 '#value' => $this->t('Remove'),
62 '#attributes' => ['class' => ['remove-item-submit', 'align-right']],
63 '#submit' => [[get_class($this), 'removeSubmit']],
65 'callback' => [get_class($this), 'getWidgetElementAjax'],
66 'wrapper' => $this->getWrapperId(),
79 protected function formMultipleElements(FieldItemListInterface $items, array &$form, FormStateInterface $form_state) {
80 $field_name = $this->fieldDefinition->getName();
81 $cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
82 $parents = $form['#parents'];
84 // Assign a unique identifier to each widget.
85 $id_prefix = implode('-', array_merge($parents, [$field_name]));
86 $wrapper_id = Html::getUniqueId($id_prefix . '-add-more-wrapper');
87 $this->setWrapperId($wrapper_id);
89 // Load the items for form rebuilds from the field state as they might not
90 // be in $form_state->getValues() because of validation limitations. Also,
91 // they are only passed in as $items when editing existing entities.
92 $field_state = static::getWidgetState($parents, $field_name, $form_state);
93 if (isset($field_state['items'])) {
94 $items->setValue($field_state['items']);
97 // Lower the 'items_count' field state property in order to prevent the
98 // parent implementation to append an extra empty item.
99 if ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
100 $field_state['items_count'] = (count($items) > 1) ? count($items) - 1 : 0;
101 static::setWidgetState($parents, $field_name, $form_state, $field_state);
104 $elements = parent::formMultipleElements($items, $form, $form_state);
107 if (isset($elements['add_more'])) {
108 // Update the HTML wrapper ID with the one generated by us.
109 $elements['#prefix'] = '<div id="' . $this->getWrapperId() . '">';
111 $add_more_button = $elements['add_more'];
112 $add_more_button['#value'] = $this->t('Add item');
113 $add_more_button['#ajax']['callback'] = [get_class($this), 'getWidgetElementAjax'];
114 $add_more_button['#ajax']['wrapper'] = $this->getWrapperId();
116 $elements['add_more'] = [
117 '#type' => 'container',
119 '#attributes' => ['class' => ['form--inline']],
120 'new_item' => parent::formElement($items, -1, [], $form, $form_state),
121 'submit' => $add_more_button,
132 public static function getWidgetElementAjax(array $form, FormStateInterface $form_state) {
133 $button = $form_state->getTriggeringElement();
135 // Go two levels up in the form, to the widgets container.
136 $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -2));
138 // Ensure the widget allows adding additional items.
139 if ($element['#cardinality'] != FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
143 // Add a DIV around the delta receiving the Ajax effect.
144 $delta = $element['#max_delta'];
145 $element[$delta]['#prefix'] = '<div class="ajax-new-content">' . (isset($element[$delta]['#prefix']) ? $element[$delta]['#prefix'] : '');
146 $element[$delta]['#suffix'] = (isset($element[$delta]['#suffix']) ? $element[$delta]['#suffix'] : '') . '</div>';
154 public static function addMoreSubmit(array $form, FormStateInterface $form_state) {
155 // During the form rebuild, formElement() will create field item widget
156 // elements using re-indexed deltas, so clear out FormState::$input to
157 // avoid a mismatch between old and new deltas. The rebuilt elements will
158 // have #default_value set appropriately for the current state of the field,
159 // so nothing is lost in doing this.
160 $button = $form_state->getTriggeringElement();
161 $parents = array_slice($button['#parents'], 0, -2);
162 NestedArray::setValue($form_state->getUserInput(), $parents, NULL);
164 // Go two levels up in the form, to the widgets container.
165 $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -2));
166 $field_name = $element['#field_name'];
167 $parents = $element['#field_parents'];
169 $submitted_values = NestedArray::getValue($form_state->getValues(), array_slice($button['#parents'], 0, -2));
171 // Check submitted values for empty items.
172 $new_values = array();
173 foreach ($submitted_values as $delta => $submitted_value) {
174 if ($delta !== 'add_more' && (isset($submitted_value['target_id']) || isset($submitted_value['entity']))) {
175 $new_values[] = $submitted_value;
178 if ($delta === 'add_more' && (isset($submitted_value['new_item']['target_id']) || isset($submitted_value['new_item']['entity']))) {
179 $new_values[] = $submitted_value['new_item'];
183 // Re-index deltas after removing empty items.
184 $submitted_values = array_values($new_values);
186 // Update form_state values.
187 NestedArray::setValue($form_state->getValues(), array_slice($button['#parents'], 0, -2), $submitted_values);
190 $field_state = static::getWidgetState($parents, $field_name, $form_state);
191 $field_state['items'] = $submitted_values;
192 static::setWidgetState($parents, $field_name, $form_state, $field_state);
194 $form_state->setRebuild();
198 * Submission handler for the "Remove" button.
200 public static function removeSubmit(array $form, FormStateInterface $form_state) {
201 $button = $form_state->getTriggeringElement();
203 // Go one level up in the form, to the single field item element container.
204 $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1));
206 $form_state->setValueForElement($element, ['target_id' => NULL, 'entity' => NULL]);
208 // Call the generic submit handler which takes care of removing the item.
209 static::addMoreSubmit($form, $form_state);
213 * Sets the unique HTML ID of the widget's wrapping element.
215 * @param string $wrapperId
216 * The unique HTML ID.
218 public function setWrapperId($wrapperId) {
219 if (!$this->wrapperId) {
220 $this->wrapperId = $wrapperId;
225 * Gets the unique HTML ID of the widget's wrapping element.
228 * The unique HTML ID.
230 public function getWrapperId() {
231 return $this->wrapperId;