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 static function defaultSettings() {
39 'link_to_entity' => FALSE,
40 ] + parent::defaultSettings();
46 public function settingsForm(array $form, FormStateInterface $form_state) {
47 $elements = parent::settingsForm($form, $form_state);
49 $elements['link_to_entity'] = [
50 '#type' => 'checkbox',
51 '#title' => $this->t('Link label to the referenced entity'),
52 '#default_value' => $this->getSetting('link'),
61 public function settingsSummary() {
62 $summary = parent::settingsSummary();
64 $settings = $this->getSettings();
65 if (!empty($settings['link_to_entity'])) {
66 $summary[] = $this->t('Link to the referenced entity');
75 public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
76 $field_name = $this->fieldDefinition->getName();
77 $parents = $form['#parents'];
78 $referenced_entities = $items->referencedEntities();
80 if (isset($referenced_entities[$delta])) {
81 if ($this->getSetting('link_to_entity') && !$referenced_entities[$delta]->isNew()) {
82 $entity_label = $referenced_entities[$delta]->toLink()->toString();
85 $entity_label = $referenced_entities[$delta]->label();
87 $id_prefix = implode('-', array_merge($parents, [$field_name, $delta]));
90 '#type' => 'container',
91 '#attributes' => ['class' => ['form--inline']],
94 '#markup' => $entity_label,
95 '#default_value' => !$referenced_entities[$delta]->isNew() ? $referenced_entities[$delta]->id() : NULL,
99 '#default_value' => $referenced_entities[$delta],
103 '#name' => strtr($id_prefix, '-', '_') . '_remove',
104 '#value' => $this->t('Remove'),
105 '#attributes' => ['class' => ['remove-item-submit', 'align-right']],
106 '#submit' => [[get_class($this), 'removeSubmit']],
108 'callback' => [get_class($this), 'getWidgetElementAjax'],
109 'wrapper' => $this->getWrapperId(),
122 protected function formMultipleElements(FieldItemListInterface $items, array &$form, FormStateInterface $form_state) {
123 $field_name = $this->fieldDefinition->getName();
124 $cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
125 $parents = $form['#parents'];
127 // Assign a unique identifier to each widget.
128 $id_prefix = implode('-', array_merge($parents, [$field_name]));
129 $wrapper_id = Html::getUniqueId($id_prefix . '-add-more-wrapper');
130 $this->setWrapperId($wrapper_id);
132 // Load the items for form rebuilds from the field state as they might not
133 // be in $form_state->getValues() because of validation limitations. Also,
134 // they are only passed in as $items when editing existing entities.
135 $field_state = static::getWidgetState($parents, $field_name, $form_state);
136 if (isset($field_state['items'])) {
137 $items->setValue($field_state['items']);
140 // Lower the 'items_count' field state property in order to prevent the
141 // parent implementation to append an extra empty item.
142 if ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
143 $field_state['items_count'] = (count($items) > 1) ? count($items) - 1 : 0;
144 static::setWidgetState($parents, $field_name, $form_state, $field_state);
147 $elements = parent::formMultipleElements($items, $form, $form_state);
150 if (isset($elements['add_more'])) {
151 // Update the HTML wrapper ID with the one generated by us.
152 $elements['#prefix'] = '<div id="' . $this->getWrapperId() . '">';
154 $add_more_button = $elements['add_more'];
155 $add_more_button['#value'] = $this->t('Add item');
156 $add_more_button['#ajax']['callback'] = [get_class($this), 'getWidgetElementAjax'];
157 $add_more_button['#ajax']['wrapper'] = $this->getWrapperId();
159 $elements['add_more'] = [
160 '#type' => 'container',
162 '#attributes' => ['class' => ['form--inline']],
163 'new_item' => parent::formElement($items, -1, [], $form, $form_state),
164 'submit' => $add_more_button,
175 public static function getWidgetElementAjax(array $form, FormStateInterface $form_state) {
176 $button = $form_state->getTriggeringElement();
178 // Go two levels up in the form, to the widgets container.
179 $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -2));
181 // Ensure the widget allows adding additional items.
182 if ($element['#cardinality'] != FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
186 // Add a DIV around the delta receiving the Ajax effect.
187 $delta = $element['#max_delta'];
188 $element[$delta]['#prefix'] = '<div class="ajax-new-content">' . (isset($element[$delta]['#prefix']) ? $element[$delta]['#prefix'] : '');
189 $element[$delta]['#suffix'] = (isset($element[$delta]['#suffix']) ? $element[$delta]['#suffix'] : '') . '</div>';
197 public static function addMoreSubmit(array $form, FormStateInterface $form_state) {
198 // During the form rebuild, formElement() will create field item widget
199 // elements using re-indexed deltas, so clear out FormState::$input to
200 // avoid a mismatch between old and new deltas. The rebuilt elements will
201 // have #default_value set appropriately for the current state of the field,
202 // so nothing is lost in doing this.
203 $button = $form_state->getTriggeringElement();
204 $parents = array_slice($button['#parents'], 0, -2);
205 NestedArray::setValue($form_state->getUserInput(), $parents, NULL);
207 // Go two levels up in the form, to the widgets container.
208 $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -2));
209 $field_name = $element['#field_name'];
210 $parents = $element['#field_parents'];
212 $submitted_values = NestedArray::getValue($form_state->getValues(), array_slice($button['#parents'], 0, -2));
214 // Check submitted values for empty items.
216 foreach ($submitted_values as $delta => $submitted_value) {
217 if ($delta !== 'add_more' && (isset($submitted_value['target_id']) || isset($submitted_value['entity']))) {
218 $new_values[] = $submitted_value;
221 if ($delta === 'add_more' && (isset($submitted_value['new_item']['target_id']) || isset($submitted_value['new_item']['entity']))) {
222 $new_values[] = $submitted_value['new_item'];
226 // Re-index deltas after removing empty items.
227 $submitted_values = array_values($new_values);
229 // Update form_state values.
230 NestedArray::setValue($form_state->getValues(), array_slice($button['#parents'], 0, -2), $submitted_values);
233 $field_state = static::getWidgetState($parents, $field_name, $form_state);
234 $field_state['items'] = $submitted_values;
235 static::setWidgetState($parents, $field_name, $form_state, $field_state);
237 $form_state->setRebuild();
241 * Submission handler for the "Remove" button.
243 public static function removeSubmit(array $form, FormStateInterface $form_state) {
244 $button = $form_state->getTriggeringElement();
246 // Go one level up in the form, to the single field item element container.
247 $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1));
249 $form_state->setValueForElement($element, ['target_id' => NULL, 'entity' => NULL]);
251 // Call the generic submit handler which takes care of removing the item.
252 static::addMoreSubmit($form, $form_state);
256 * Sets the unique HTML ID of the widget's wrapping element.
258 * @param string $wrapperId
259 * The unique HTML ID.
261 public function setWrapperId($wrapperId) {
262 if (!$this->wrapperId) {
263 $this->wrapperId = $wrapperId;
268 * Gets the unique HTML ID of the widget's wrapping element.
271 * The unique HTML ID.
273 public function getWrapperId() {
274 return $this->wrapperId;