diff --git a/src/Plugin/Field/FieldWidget/ViewfieldWidgetSelect.php b/src/Plugin/Field/FieldWidget/ViewfieldWidgetSelect.php new file mode 100644 index 0000000..9536b65 --- /dev/null +++ b/src/Plugin/Field/FieldWidget/ViewfieldWidgetSelect.php @@ -0,0 +1,305 @@ +fieldDefinition->getType(); + $item = $items[$delta]; + + $element['target_id'] = parent::formElement($items, $delta, $element, $form, $form_state); + $element['target_id']['#field_type'] = $field_type; + $element['target_id']['#field_item'] = $item; + $element['target_id']['#description'] = $this->t('View name.'); + $element['target_id']['#ajax'] = [ + 'callback' => [$this, 'ajaxGetDisplayOptions'], + 'event' => 'change', + 'progress' => [ + 'type' => 'throbber', + 'message' => $this->t('Retrieving view displays.'), + ], + ]; + + // Set up options for allowed views. + $empty_label = $this->getEmptyLabel() ?: $this->t('- None -'); + // Always allow '_none' for non-required fields or second and greater delta. + $none_option = (!$this->fieldDefinition->isRequired() || $delta > 0) ? ['_none' => $empty_label] : []; + $element['target_id']['#options'] = array_merge($none_option, $item->getViewOptions()); + $element['target_id']['#multiple'] = FALSE; + + // Build an array of keys to retrieve values from $form_state. + $form_state_keys = [$this->fieldDefinition->getName(), $delta]; + if (!empty($form['#parents'])) { + $form_state_keys = array_merge($form['#parents'], $form_state_keys); + } + + // Assign default values. + $default_target_id = NULL; + $display_id_options = NULL; + $default_display_id = NULL; + $default_arguments = NULL; + $default_items_to_display = NULL; + $item_value = $item->getValue(); + + // Set initial default values from the item if they exist. + if (isset($item_value['target_id'])) { + $default_target_id = $item_value['target_id']; + $display_id_options = $item->getDisplayOptions($item_value['target_id']); + $default_display_id = $item_value['display_id']; + $default_arguments = $item_value['arguments']; + $default_items_to_display = $item_value['items_to_display']; + } + + // Set default values based on values from form_state, if they exist. + $values = $form_state->getValue($this->fieldDefinition->getName()); + if (!empty($values[$delta])) { + $default_target_id = $values[$delta]['target_id']; + $default_display_id = $values[$delta]['display_id']; + $default_arguments = $values[$delta]['arguments']; + $display_id_options = $item->getDisplayOptions($default_target_id); + $default_items_to_display = $values[$delta]['items_to_display']; + } + + // #default_value needs special handling, otherwise it consists of an array + // of values corresponding to field items, one for each #delta. + $element['target_id']['#default_value'] = $default_target_id; + + // Construct CSS class to target ajax callback. + $display_id_class = $this->createDisplayClass($form_state_keys); + + // Use primary target_id field to control visibility of secondary ones. + $field_key = $form_state_keys[0] . '[' . implode('][', array_slice($form_state_keys, 1)) . ']'; + $primary_field_name = $field_key . '[target_id]'; + $primary_field_visible_test = [':input[name="' . $primary_field_name . '"]' => ['!value' => '_none']]; + + $element['display_id'] = [ + '#title' => $this->t('Display'), + '#type' => 'select', + '#options' => $display_id_options, + '#default_value' => $default_display_id, + '#description' => $this->t('View display to be used.'), + '#attributes' => ['class' => [$display_id_class]], + '#weight' => 10, + '#states' => ['visible' => $primary_field_visible_test], + ]; + + $element['view_options'] = [ + '#type' => 'details', + '#title' => $this->t('Advanced options'), + '#weight' => 20, + '#open' => false, + '#parents' => $form_state_keys, + '#states' => [ + 'open' => [ + [':input[name="' . $field_key .'[arguments]"]' => ['filled' => TRUE]], + [':input[name="' . $field_key .'[items_to_display]"]' => ['filled' => TRUE]], + ], + 'visible' => $primary_field_visible_test, + ], + ]; + + $element['view_options']['arguments'] = [ + '#title' => $this->t('Arguments'), + '#type' => 'textfield', + '#default_value' => $default_arguments, + '#description' => $this->t('Separate contextual filters with a "/". Each filter may use "+" or "," for multi-value arguments.
This field supports tokens.'), + '#weight' => 20, + '#states' => ['visible' => $primary_field_visible_test], + '#maxlength' => 255, + ]; + + // Get entity types from the $items. + $entity_type = $items->getEntity()->getEntityTypeId(); + + // Token expects the value "term" for taxonomy_term entities. + if ($entity_type == 'taxonomy_term') { + $entity_type = 'term'; + } + + $element['view_options']['token_help'] = [ + '#type' => 'item', + '#weight' => 30, + '#states' => ['visible' => $primary_field_visible_test], + 'tokens' => [ + '#theme' => 'token_tree_link', + '#token_types' => [$entity_type], + ], + ]; + + $element['view_options']['items_to_display'] = [ + '#title' => $this->t('Items to display'), + '#type' => 'textfield', + '#default_value' => $default_items_to_display, + '#description' => $this->t('Override the number of items to display. This also disables the pager if one is configured. Leave empty for default limit.'), + '#weight' => 40, + '#states' => ['visible' => $primary_field_visible_test], + ]; + + $element['#attached']['library'][] = 'viewfield/viewfield'; + + return $element; + } + + /** + * {@inheritdoc} + */ + protected function formMultipleElements(FieldItemListInterface $items, array &$form, FormStateInterface $form_state) { + $elements = parent::formMultipleElements($items, $form, $form_state); + // Must always show fields on configuration form. + if (!$this->isDefaultValueWidget($form_state) && $this->getFieldSetting('force_default')) { + $elements['#access'] = FALSE; + } + + $max_delta = $elements['#max_delta']; + $is_multiple = $elements['#cardinality_multiple']; + + if ($is_multiple) { + for ($delta = 0; $delta <= $max_delta; $delta++) { + $element = &$elements[$delta]; + // Change title to 'View #'. + $element['target_id']['#title'] = $this->t('View @number', ['@number' => $delta + 1]); + // Force title display. + $element['target_id']['#title_display'] = 'before'; + } + } + else { + // $max_delta == 0 for this case. + $element = &$elements[0]; + // Change title to simply 'View'. + $element['target_id']['#title'] = $this->t('View'); + // Wrap single values in a fieldset unless on the default settings form, + // as long as the field is visible (!force_default). + if (!$this->isDefaultValueWidget($form_state) && !$this->getFieldSetting('force_default')) { + $element += [ + '#type' => 'fieldset', + '#title' => $this->fieldDefinition->getLabel(), + ]; + } + } + + return $elements; + } + + /** + * Overridden form validation handler for widget elements. + * + * Save selected target_id as a single item, since there will be at most one. + * This prevents the value from being deeply nested inside $form_state. + * + * @param array $element + * The form element. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. + * + * @see OptionsWidgetBase::validateElement() + */ + public static function validateElement(array $element, FormStateInterface $form_state) { + if ($element['#required'] && $element['#value'] == '_none') { + $form_state->setError($element, t('@name field is required.', ['@name' => $element['#title']])); + } + + // Massage submitted form values. + // Drupal\Core\Field\WidgetBase::submit() expects values as + // an array of values keyed by delta first, then by column, while our + // widgets return the opposite. + if (is_array($element['#value'])) { + $values = array_values($element['#value']); + } + else { + $values = [$element['#value']]; + } + + // Filter out the 'none' option. Use a strict comparison, because + // 0 == 'any string'. + $index = array_search('_none', $values, TRUE); + if ($index !== FALSE) { + unset($values[$index]); + } + + $target_id = !empty($values[0]) ? $values[0] : NULL; + $form_state->setValueForElement($element, $target_id); + } + + public function massageFormValues(array $values, array $form, FormStateInterface $form_state) { + $values = parent::massageFormValues($values, $form, $form_state); // TODO: Change the autogenerated stub + foreach ($values as $key => $value) { + } + return $values; + } + + /** + * {@inheritdoc} + */ + public function errorElement(array $element, ConstraintViolationInterface $error, array $form, FormStateInterface $form_state) { + $element = $element; + return $element['target_id']; + } + + /** + * Ajax callback to retrieve display IDs. + * + * @param array $form + * The form from which the display IDs are being requested. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state of the form. + * + * @return \Drupal\Core\Ajax\AjaxResponse + * The Ajax response. + */ + public function ajaxGetDisplayOptions(array &$form, FormStateInterface $form_state) { + $trigger = $form_state->getTriggeringElement(); + $form_state_keys = array_slice($trigger['#parents'], 0, -1); + $form_state_value = $form_state->getValue($form_state_keys); + $display_options = $trigger['#field_item']->getDisplayOptions($form_state_value['target_id']); + + $html = ''; + foreach ($display_options as $key => $value) { + $html .= ''; + } + $html = '' . $html . ''; + + // Create a class selector for Ajax response. + $selector = '.' . $this->createDisplayClass($form_state_keys); + $response = new AjaxResponse(); + $response->addCommand(new HtmlCommand($selector, $html)); + $response->addCommand(new InvokeCommand($selector, 'ajaxEnableElements')); + $form_state->setRebuild(TRUE); + + return $response; + } + + /** + * Produce a class for a display input field. + * + * @param array $components + * An array of class components to be concatenated. + * + * @return string + * The display input field class. + */ + protected function createDisplayClass(array $components) { + return implode('-', $components) . '-display-id'; + } + +} diff --git a/templates/viewfield-formatter-default.html.twig b/templates/viewfield-formatter-default.html.twig index 15fd2a9..809937f 100644 --- a/templates/viewfield-formatter-default.html.twig +++ b/templates/viewfield-formatter-default.html.twig @@ -11,10 +11,10 @@ * @ingroup themeable */ #} -{% spaceless %} +{% apply spaceless %}
{% if output %}
{{ output }}
{% endif %}
-{% endspaceless %} +{% endapply %} diff --git a/viewfield.info.yml b/viewfield.info.yml index 5f25a88..bed2a19 100644 --- a/viewfield.info.yml +++ b/viewfield.info.yml @@ -3,6 +3,7 @@ type: module description: 'Defines a field type to display a view.' version: VERSION core: 8.x +core_version_requirement: ^8 || ^9 || ^10 dependencies: - views - field diff --git a/viewfield.module b/viewfield.module index 56d3c68..8c59885 100644 --- a/viewfield.module +++ b/viewfield.module @@ -25,7 +25,7 @@ function viewfield_theme() { function viewfield_preprocess_viewfield_formatter_default(&$variables) { $element = $variables['element']; $view_el = $element['#view']->preview($element['#view_display'], array($element['#view_arguments'])); - $output = render ($view_el); + $output = \Drupal::service('renderer')->render($view_el); $variables['output'] = $output; }