You are here

class WebformOptionsCustom in Webform 6.x

Same name in this branch
  1. 6.x modules/webform_options_custom/src/Element/WebformOptionsCustom.php \Drupal\webform_options_custom\Element\WebformOptionsCustom
  2. 6.x modules/webform_options_custom/src/Entity/WebformOptionsCustom.php \Drupal\webform_options_custom\Entity\WebformOptionsCustom
  3. 6.x modules/webform_options_custom/src/Plugin/WebformElement/WebformOptionsCustom.php \Drupal\webform_options_custom\Plugin\WebformElement\WebformOptionsCustom
Same name and namespace in other branches
  1. 8.5 modules/webform_options_custom/src/Element/WebformOptionsCustom.php \Drupal\webform_options_custom\Element\WebformOptionsCustom

Provides an element for a selecting custom options from HTML or SVG markup.

Plugin annotation

@FormElement("webform_options_custom");

Hierarchy

Expanded class hierarchy of WebformOptionsCustom

1 file declares its use of WebformOptionsCustom
WebformOptionsCustom.php in modules/webform_options_custom/src/Entity/WebformOptionsCustom.php
1 #type use of WebformOptionsCustom
WebformOptionsCustom::getElement in modules/webform_options_custom/src/Entity/WebformOptionsCustom.php
Get the custom options element.

File

modules/webform_options_custom/src/Element/WebformOptionsCustom.php, line 25

Namespace

Drupal\webform_options_custom\Element
View source
class WebformOptionsCustom extends FormElement implements WebformOptionsCustomInterface {
  use WebformCompositeFormElementTrait;

  /**
   * The properties of the element.
   *
   * @var array
   */
  protected static $properties = [
    '#title',
    '#options',
    '#default_value',
    '#multiple',
    '#attributes',
    '#empty_option',
    '#empty_value',
    '#select2',
    '#chosen',
    // NOTE:
    // Choices is not supported by custom options because of <option> being
    // removed inside the <select>.
    // @see https://github.com/jshjohnson/Choices/issues/601
    '#placeholder',
    '#help_display',
    '#size',
    '#required',
    '#required_error',
  ];

  /**
   * {@inheritdoc}
   */
  public function getInfo() {
    $class = get_class($this);
    return [
      '#input' => TRUE,
      '#process' => [
        [
          $class,
          'processWebformOptionsCustom',
        ],
        [
          $class,
          'processAjaxForm',
        ],
      ],
      '#pre_render' => [
        [
          $class,
          'preRenderWebformCompositeFormElement',
        ],
      ],
      '#options_custom' => NULL,
      '#options' => [],
      '#template' => '',
      '#value_attributes' => 'data-option-value,data-value,data-id,id',
      '#text_attributes' => 'data-option-text,data-text,data-name,name,title',
      '#fill' => TRUE,
      '#zoom' => FALSE,
      '#tooltip' => FALSE,
      '#show_select' => TRUE,
    ];
  }

  /**
   * {@inheritdoc}
   */
  public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
    $element += [
      '#default_value' => '',
    ];
    if ($input === FALSE) {
      return [
        'value' => $element['#default_value'],
      ];
    }
    else {
      if (isset($input['select'])) {
        $input['value'] = $input['select'];
      }
      return $input;
    }
  }

  /**
   * Processes an 'other' element.
   *
   * See select list webform element for select list properties.
   *
   * @see \Drupal\Core\Render\Element\Select
   */
  public static function processWebformOptionsCustom(&$element, FormStateInterface $form_state, &$complete_form) {

    // Load config entity and set the element's #options and #template.
    static::setTemplateOptions($element);

    // Sanitize option descriptions which may have been altered.
    // Note: Option text is escaped via JavaScript.
    // @see webform_options_custom.element.js#initializeTemplateTooltip
    $descriptions = [];
    foreach ($element['#options'] as $option_value => $option_text) {
      if (WebformOptionsHelper::hasOptionDescription($option_text)) {
        list($option_text, $option_description) = WebformOptionsHelper::splitOption($option_text);
        $element['#options'][$option_value] = $option_text;
        $descriptions[$option_value] = Xss::filterAdmin($option_description);
      }
    }

    // Get inline template context.
    $template_context = WebformArrayHelper::removePrefix($element) + [
      'descriptions' => $descriptions,
    ];
    $element['#tree'] = TRUE;

    // Select menu.
    $element['select'] = [
      '#type' => 'select',
      '#options' => $element['#options'],
      '#title' => $element['#title'],
      '#webform_element' => TRUE,
      '#title_display' => 'invisible',
      '#error_no_message' => TRUE,
    ];
    $properties = static::$properties;
    $element['select'] += array_intersect_key($element, array_combine($properties, $properties));

    // Apply #parents to select element.
    if (isset($element['#parents'])) {
      $element['select']['#parents'] = array_merge($element['#parents'], [
        'select',
      ]);
    }

    // Initialize the select to allow for webform enhancements.

    /** @var \Drupal\webform\Plugin\WebformElementManagerInterface $element_manager */
    $element_manager = \Drupal::service('plugin.manager.webform.element');
    $element_manager
      ->buildElement($element['select'], $complete_form, $form_state);
    if (preg_match('/(\\{\\{|\\{%)/', $element['#template'])) {

      // Build template using Twig when Twig syntax is found.
      $element['template'] = [
        '#type' => 'inline_template',
        '#template' => $element['#template'],
        '#context' => $template_context,
        '#prefix' => '<div class="webform-options-custom-template">',
        '#suffic' => '</div>',
      ];
    }
    else {

      // Build template using markup.
      $element['template'] = [
        '#markup' => Markup::create($element['#template']),
        '#prefix' => '<div class="webform-options-custom-template">',
        '#suffic' => '</div>',
      ];
    }

    // Set classes.
    $element['#attributes']['class'][] = 'js-webform-options-custom';
    $element['#attributes']['class'][] = 'webform-options-custom';
    if (!empty($element['#options_custom'])) {
      $webform_options_custom_class = Html::getClass($element['#options_custom']);
      $element['#attributes']['class'][] = 'js-webform-options-custom--' . $webform_options_custom_class;
      $element['#attributes']['class'][] = 'webform-options-custom--' . $webform_options_custom_class;
    }

    // Apply the element id to the wrapper so that inline form errors point
    // to the correct element.
    $element['#attributes']['id'] = $element['#id'];

    // Set SVG fill, zoom, tooltip, and show/hide select.
    if ($element['#fill']) {
      $element['#attributes']['data-fill'] = TRUE;
    }
    if ($element['#zoom']) {
      $element['#attributes']['data-zoom'] = TRUE;
    }
    if ($element['#tooltip']) {
      $element['#attributes']['data-tooltip'] = TRUE;
    }
    if (empty($element['#show_select'])) {
      $element['#attributes']['data-select-hidden'] = TRUE;
    }

    // Set descriptions.
    if ($descriptions) {
      $element['#attributes']['data-descriptions'] = Json::encode($descriptions);
    }

    // Remove options to prevent option validation on the element.
    unset($element['#options']);

    // Add validate callback.
    $element += [
      '#element_validate' => [],
    ];
    array_unshift($element['#element_validate'], [
      get_called_class(),
      'validateWebformOptionsCustom',
    ]);

    // Attach libraries.
    $element['#attached']['library'][] = 'webform_options_custom/webform_options_custom.element';
    if ($element['#zoom']) {
      $element['#attached']['library'][] = 'webform_options_custom/libraries.svg-pan-zoom';
    }

    // Process states.
    WebformFormHelper::processStates($element, '#wrapper_attributes');
    return $element;
  }

  /**
   * Validates an other element.
   */
  public static function validateWebformOptionsCustom(&$element, FormStateInterface $form_state, &$complete_form) {
    $value = NestedArray::getValue($form_state
      ->getValues(), $element['select']['#parents']);

    // Determine if the element has multiple values.
    $is_multiple = empty($element['#multiple']) ? FALSE : TRUE;

    // Determine if the return value is empty.
    if ($is_multiple) {
      $is_empty = empty($value) ? TRUE : FALSE;
    }
    else {
      $is_empty = $value === '' || $value === NULL ? TRUE : FALSE;
    }

    // Validate on elements with #access.
    if (Element::isVisibleElement($element) && !empty($element['#required']) && $is_empty) {
      WebformElementHelper::setRequiredError($element, $form_state);
    }
    $form_state
      ->setValueForElement($element['select'], NULL);
    $element['#value'] = $value;
    $form_state
      ->setValueForElement($element, $value);
  }

  /****************************************************************************/

  // Helper methods.

  /****************************************************************************/

  /**
   * Set a custom options element #options property.
   *
   * @param array $element
   *   A custom options element.
   */
  public static function setOptions(array &$element) {

    // Do nothing.
  }

  /**
   * Set a custom options element #options property.
   *
   * @param array $element
   *   A custom options element.
   */
  public static function setTemplateOptions(array &$element) {
    if (isset($element['#_options_custom'])) {
      return;
    }
    $element['#_options_custom'] = TRUE;

    // Set options. Used by entity references.
    static::setOptions($element);

    // Load custom options from config entity.
    if (!empty($element['#options_custom'])) {
      $webform_option_custom = WebformOptionsCustomEntity::load($element['#options_custom']);
      if ($webform_option_custom) {
        $custom_element = $webform_option_custom
          ->getElement();
        $element += $custom_element;
        $element['#options'] += $custom_element['#options'];
      }
    }

    // Set default properties.
    $element += [
      '#options' => [],
      '#value_attributes' => 'data-option-value,data-value,data-id,id',
      '#text_attributes' => 'data-option-text,data-text,data-name,name,title',
    ];

    // Get options.
    $options =& $element['#options'];

    // Build options by text look up.
    $options_by_text = [];
    foreach ($options as $option_value => $option_text) {
      $option_description = '';
      if (WebformOptionsHelper::hasOptionDescription($option_text)) {
        list($option_text, $option_description) = WebformOptionsHelper::splitOption($option_text);
      }
      $options_by_text[$option_text] = [
        'value' => $option_value,
        'text' => $option_text,
        'description' => $option_description,
      ];
    }

    // Get option value and text attributes.
    $value_attribute_name = NULL;
    if ($element['#value_attributes']) {
      $value_attributes = preg_split('/\\s*,\\s*/', trim($element['#value_attributes']));
      foreach ($value_attributes as $value_attribute) {
        if (strpos($element['#template'], $value_attribute) !== FALSE) {
          $value_attribute_name = $value_attribute;
          break;
        }
      }
    }
    $text_attribute_name = NULL;
    if ($element['#text_attributes']) {
      $text_attributes = preg_split('/\\s*,\\s*/', trim($element['#text_attributes']));
      foreach ($text_attributes as $text_attribute) {
        if (strpos($element['#template'], $text_attribute) !== FALSE) {
          $text_attribute_name = $text_attribute;
          break;
        }
      }
    }
    $custom_attributes = [];
    if ($value_attribute_name) {
      $custom_attributes[] = $value_attribute_name;
    }
    if ($text_attribute_name) {
      $custom_attributes[] = $text_attribute_name;
    }
    if (empty($custom_attributes)) {
      return;
    }

    // Combine text and value attributes into an Xpath query that finds all
    // DOM element which contain any of the attributes.
    $css_attributes = array_map(function ($value) {
      return 'descendant-or-self::*[@' . $value . ']';
    }, $custom_attributes);
    $xpath_expression = implode(' | ', $css_attributes);

    // Remove XML tag from SVG file.
    $xml_tag = NULL;
    $template = $element['#template'];
    if (preg_match('/<\\?xml[^>]+\\?>\\s+/', $element['#template'], $match)) {
      $xml_tag = $match[0];
      $template = str_replace($xml_tag, '', $template);
    }
    $dom = Html::load($template);
    $xpath = new \DOMXPath($dom);
    foreach ($xpath
      ->query($xpath_expression) as $dom_node) {
      if (in_array($dom_node->tagName, [
        'svg',
      ])) {
        continue;
      }
      $dom_attributes = [];
      foreach ($dom_node->attributes as $attribute_name => $attribute_node) {

        /** @var \DOMNode $attribute_node */
        $dom_attributes[$attribute_name] = $attribute_node->nodeValue;
      }
      $dom_attributes += [
        $value_attribute_name => '',
        $text_attribute_name => '',
      ];

      // Get value and text attribute.
      $option_value = $dom_attributes[$value_attribute_name];
      $option_text = $dom_attributes[$text_attribute_name];

      // Set missing options value based on options text.
      if ($option_value === '' && $option_text !== '') {

        // Look up options value using the option's text.
        if (isset($options_by_text[$option_text])) {
          $option_value = $options_by_text[$option_text]['value'];
        }
        else {
          $option_value = $option_text;
        }
      }

      // Append value and text to the options array.
      $options += [
        $option_value => $option_text ?: $option_value,
      ];

      // Always set the data-option-value attribute.
      // Note: The select menu's option text is the canonical source for
      // the option text.
      $dom_node
        ->setAttribute('data-option-value', $option_value);
    }

    // Set template with tweaked or additional attributes.
    $element['#template'] = $xml_tag . Html::serialize($dom);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
DependencySerializationTrait::$_entityStorages protected property
DependencySerializationTrait::$_serviceIds protected property
DependencySerializationTrait::__sleep public function 2
DependencySerializationTrait::__wakeup public function 2
FormElement::processAutocomplete public static function Adds autocomplete functionality to elements.
FormElement::processPattern public static function #process callback for #pattern form element property.
FormElement::validatePattern public static function #element_validate callback for #pattern form element property.
MessengerTrait::$messenger protected property The messenger. 27
MessengerTrait::messenger public function Gets the messenger. 27
MessengerTrait::setMessenger public function Sets the messenger.
PluginBase::$configuration protected property Configuration information passed into the plugin. 1
PluginBase::$pluginDefinition protected property The plugin implementation definition. 1
PluginBase::$pluginId protected property The plugin_id.
PluginBase::DERIVATIVE_SEPARATOR constant A string which is used to separate base plugin IDs from the derivative ID.
PluginBase::getBaseId public function Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface::getBaseId
PluginBase::getDerivativeId public function Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface::getDerivativeId
PluginBase::getPluginDefinition public function Gets the definition of the plugin implementation. Overrides PluginInspectionInterface::getPluginDefinition 2
PluginBase::getPluginId public function Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface::getPluginId
PluginBase::isConfigurable public function Determines if the plugin is configurable.
PluginBase::__construct public function Constructs a \Drupal\Component\Plugin\PluginBase object. 98
RenderElement::preRenderAjaxForm public static function Adds Ajax information about an element to communicate with JavaScript.
RenderElement::preRenderGroup public static function Adds members of this group as actual elements for rendering.
RenderElement::processAjaxForm public static function Form element processing handler for the #ajax form property. 1
RenderElement::processGroup public static function Arranges elements into groups.
RenderElement::setAttributes public static function Sets a form element's class attribute. Overrides ElementInterface::setAttributes
StringTranslationTrait::$stringTranslation protected property The string translation service. 4
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.
WebformCompositeFormElementTrait::preRenderWebformCompositeFormElement public static function Adds form element theming to an element if its title or description is set. 1
WebformOptionsCustom::$properties protected static property The properties of the element.
WebformOptionsCustom::getInfo public function Returns the element properties for this element. Overrides ElementInterface::getInfo
WebformOptionsCustom::processWebformOptionsCustom public static function Processes an 'other' element.
WebformOptionsCustom::setOptions public static function Set a custom options element #options property.
WebformOptionsCustom::setTemplateOptions public static function Set a custom options element #options property. Overrides WebformOptionsCustomInterface::setTemplateOptions 1
WebformOptionsCustom::validateWebformOptionsCustom public static function Validates an other element.
WebformOptionsCustom::valueCallback public static function Determines how user input is mapped to an element's #value property. Overrides FormElement::valueCallback