You are here

Form.php in GridStack 8.2

File

src/Plugin/gridstack/stylizer/Form.php
View source
<?php

namespace Drupal\gridstack\Plugin\gridstack\stylizer;

use Drupal\Component\Serialization\Json;
use Drupal\Core\Form\FormStateInterface;
use Drupal\gridstack\GridStackDefault;
use Drupal\gridstack\Entity\GridStackVariant;

/**
 * Provides the form elements.
 *
 * @GridStackStylizer(
 *   id = "form",
 *   label = @Translation("Form")
 * )
 *
 * @todo move functionality into GridStackAdminStylizer for admin.
 */
class Form extends Help {

  /**
   * {@inheritdoc}
   */
  public function buildConfigurationForm($optionset, FormStateInterface $form_state, array $settings, array $extras = []) {
    $context = $settings['_scope'];
    $element = [];

    // Provides global elements.
    if ($context == GridStackDefault::ROOT) {
      $settings['_fullwidth'] = TRUE;
      $element = array_merge($element, $this
        ->globalForm($optionset, $form_state, $settings, $extras));
    }

    // Provides wrapper elements.
    $element = array_merge($element, $this
      ->wrapperForm($optionset, $form_state, $settings, $extras));
    foreach ([
      'target_id',
      '_fullwidth',
    ] as $key) {
      $element[$key]['#type'] = 'hidden';
      $element[$key]['#default_value'] = isset($settings[$key]) ? $settings[$key] : '';
    }
    $element['target_id']['#attributes']['data-gs-media-storage'] = $context;

    // Provides ranges, colors, and extra elements.
    $element = array_merge($element, $this
      ->styleForm($optionset, $form_state, $settings, $extras));
    return $element;
  }

  /**
   * {@inheritdoc}
   */
  public function closingForm(array &$form, array $settings) {

    // Allow the current selection to be set in a hidden field so the selection
    // can be passed between different states of the form. This field is filled
    // via JavaScript so the default value should be empty.
    // @see Drupal.behaviors.MediaLibraryItemSelection
    $form['current_selection'] = [
      '#type' => 'hidden',
      '#default_value' => '',
      '#attributes' => [
        'class' => [
          'js-media-library-add-form-current-selection',
        ],
      ],
    ];
    if ($palettes = $this
      ->getColorPalettes()) {
      $form['color_palettes'] = $this
        ->paletteElement($palettes);
      if ($pos = $this
        ->config('palettes_pos')) {
        $form['color_palettes']['#attributes']['class'][] = 'form-wrapper--color-palettes--offset';
        $form['color_palettes']['#attributes']['class'][] = 'form-wrapper--color-palettes--' . $pos;
      }
    }
    if (!$this
      ->config('helpless')) {
      $form['help'] = [];
      $form['help'] = array_merge($form['help'], $this
        ->helpElement());
    }
    $form['#attached']['library'][] = 'gridstack/admin_modal';
    if (!empty($settings['access_media']) && ($library = $this
      ->getMediaLibraryTheme())) {
      $form['#attached']['library'][] = $library;
    }
  }

  /**
   * {@inheritdoc}
   */
  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
    parent::validateConfigurationForm($form, $form_state);
    $settings = $form_state
      ->getValue('settings');
    $form_state
      ->setValue([
      'settings',
      'gridnative',
    ], (bool) $settings['gridnative']);
    $form_state
      ->setValue([
      'settings',
      '_fullwidth',
    ], (bool) $settings['_fullwidth']);
    $key = [
      'settings',
      'wrapper_classes',
    ];
    $this
      ->massageClasses($key, $settings, $form_state);
    $key = [
      'settings',
      'row_classes',
    ];
    $this
      ->massageRowClasses($key, $settings, $form_state);
    $key = [
      'settings',
      'styles',
      'metadata',
    ];
    $this
      ->massageMetadata($key, $settings, $form_state);
    foreach ([
      'animations',
      'colors',
      'extras',
    ] as $key) {
      $setting_name = [
        'settings',
        'styles',
        $key,
      ];
      $this
        ->massageArrayValues($key, $setting_name, $form_state);
    }
    $regions = $form_state
      ->getValue('regions');
    foreach ($regions as $name => $region) {
      $form_state
        ->setValue([
        'regions',
        $name,
        '_fullwidth',
      ], (bool) $region['_fullwidth']);
      $key = [
        'regions',
        $name,
        'wrapper_classes',
      ];
      $this
        ->massageClasses($key, $region, $form_state);
      $key = [
        'regions',
        $name,
        'row_classes',
      ];
      $this
        ->massageRowClasses($key, $region, $form_state);
      $key = [
        'regions',
        $name,
        'styles',
        'metadata',
      ];
      $this
        ->massageMetadata($key, $region, $form_state);
      foreach ([
        'animations',
        'colors',
        'extras',
      ] as $key) {
        $setting_name = [
          'regions',
          $name,
          'styles',
          $key,
        ];
        $this
          ->massageArrayValues($key, $setting_name, $form_state);
      }
    }
  }

  /**
   * Massages media metadata.
   */
  protected function massageMetadata($key, array $settings, FormStateInterface $form_state) {
    $mid = $this
      ->saveMediaId($settings, $form_state);
    $data = $this
      ->getMediaData(NULL, $mid);
    $form_state
      ->setValue($key, $data ? Json::encode($data) : '');
  }

  /**
   * Cleans up array form.
   */
  protected function massageArrayValues($key, array $name, FormStateInterface $form_state) {
    $values = $form_state
      ->getValue($name);

    // Respects rgba, aside from hexcode. Only remove default black.
    if ($key == 'colors') {
      foreach ($values as &$value) {
        if ($value == "#000000") {
          $value = '';
        }
      }
    }
    $form_state
      ->setValue($name, array_filter($values));
  }

  /**
   * Cleans up styles form.
   */
  public function cleanupStyles(array &$settings = []) {
    $data = [
      'open_button',
      'media_library_update_widget',
      'media_library_selection',
      'remove_button',
      'selection',
    ];
    foreach ($data as $key) {
      unset($settings[$key]);
    }
  }

  /**
   * Returns merged classes for .row or .box__content.
   */
  protected function massageClasses($name, array $settings, FormStateInterface $form_state) {
    $wrapper_class = isset($settings['wrapper_classes']) ? $settings['wrapper_classes'] : '';
    $selected_classes = isset($settings['preset_classes']) ? $settings['preset_classes'] : [];
    $classes = [];
    if ($classes = $this->manager
      ->getMergedClasses(TRUE)) {
      $classes = array_combine($classes, $classes);
    }
    $this
      ->massageAllClasses($form_state, $name, $wrapper_class, $selected_classes, $classes);
  }

  /**
   * Returns merged classes for .row only.
   */
  protected function massageRowClasses($name, array $settings, FormStateInterface $form_state) {
    $wrapper_class = isset($settings['row_classes']) ? $settings['row_classes'] : '';
    $selected_classes = isset($settings['preset_row_classes']) ? $settings['preset_row_classes'] : [];
    $classes = [];
    if ($classes = $this->manager
      ->engineManager()
      ->getClassOptions('row')) {
      $classes = array_combine($classes, $classes);
    }
    $this
      ->massageAllClasses($form_state, $name, $wrapper_class, $selected_classes, $classes);
  }

  /**
   * Returns merged $[wrapper|row]_classes and $selected_classes.
   *
   * We do this since we don't store massive class options, instead interpolated
   * into the existing string [wrapper|row]_classes option during form
   * validation to avoid similar logic at front-end.
   */
  protected function massageAllClasses(FormStateInterface $form_state, $name, $wrapper_class = '', array $selected_classes = [], array $classes = []) {
    $wrapper_classes = $wrapper_class ? array_map('trim', explode(" ", $wrapper_class)) : [];
    $wrapper_classes = $wrapper_class ? array_combine($wrapper_classes, $wrapper_classes) : [];
    $selected_classes = array_filter($selected_classes);
    if ($selected_classes) {
      $selected_classes = array_values($selected_classes);
      $selected_classes = array_combine($selected_classes, $selected_classes);
    }

    // If $selected_classes are left empty, remove it from $wrapper_classes.
    // Ensures to not remove custom defined classes.
    if ($wrapper_classes) {
      foreach ($wrapper_classes as $key => $value) {
        if (isset($classes[$key]) && !isset($selected_classes[$key])) {
          unset($wrapper_classes[$key]);
        }
      }
    }
    $wrapper_classes = $selected_classes ? array_merge($wrapper_classes, $selected_classes) : $wrapper_classes;
    $wrapper_classes = array_unique(array_values($wrapper_classes));
    $merged = $wrapper_classes ? implode(" ", $wrapper_classes) : '';
    $form_state
      ->setValue($name, $merged);
  }

  /**
   * {@inheritdoc}
   */
  protected function globalForm($optionset, FormStateInterface $form_state, array $settings, array $extras = []) {
    $element = [];
    $field_options = $extras['field_options'];
    $framework = $optionset
      ->isFramework();

    // Provides unique variant ID.
    $vid = $this
      ->getVariantUniqueId($optionset);
    if (empty($settings['vid']) && GridStackVariant::load($vid)) {
      $vid = $this
        ->getVariantUniqueId($optionset);
    }
    $element['global'] = [
      '#type' => 'details',
      '#open' => FALSE,
      '#title' => $this
        ->t('Main settings'),
    ];
    if (!$this
      ->config('skinless')) {
      $element['global']['skin'] = [
        '#type' => 'select',
        '#title' => $this
          ->t('Skin'),
        '#options' => $this->manager
          ->skinManager()
          ->getSkinOptions(),
        '#empty_option' => $this
          ->t('- None -'),
        '#default_value' => $settings['skin'],
      ];
    }
    $empty_desc = $field_options ? '' : ' ' . $this
      ->t('Create one unlimited multi-value Media if none exists.');
    $element['global']['field_name'] = [
      '#type' => 'select',
      '#title' => $this
        ->t('Media field'),
      '#options' => $field_options,
      '#empty_option' => $this
        ->t('- None -'),
      '#description' => $this
        ->t('Unlimited <code>Media</code> field to select media from.') . $empty_desc,
      '#default_value' => $settings['field_name'],
    ];
    $element['global']['vm'] = [
      '#type' => 'select',
      '#title' => $this
        ->t('Vertical margin'),
      '#options' => GridStackDefault::breakpoints(),
      '#empty_option' => $this
        ->t('- None -'),
      '#description' => $this
        ->t('Avoid overlapping regions globally, unless desired. If bad, use region settings instead.'),
      '#default_value' => $settings['vm'],
      '#access' => !empty($framework),
      '#attributes' => [
        'class' => [
          'form-item--vm',
        ],
      ],
    ];
    $element['global']['gridnative'] = [
      '#type' => 'checkbox',
      '#title' => $this
        ->t('Use native CSS Grid'),
      '#description' => $this
        ->t('<b>Experimental!</b> Check to replace any js-driven (gridstack, masonry, packery, isotope) layouts with native browser Grid layout. Check out <a href=":url">CSS Grid browser supports</a> relevant to your visitors. Uncheck if any issue.', [
        ':url' => 'https://caniuse.com/#feat=css-grid',
      ]),
      '#default_value' => $settings['gridnative'],
      '#access' => empty($framework),
    ];
    $icons = [];
    $element['global']['icon']['#theme'] = 'item_list';
    $element['global']['icon']['#wrapper_attributes']['class'][] = 'item-list--icon';
    if ($uri = $optionset
      ->getIconUri()) {
      $icons['base'] = [
        '#theme' => 'image',
        '#uri' => $uri,
        '#alt' => $this
          ->t('Thumbnail'),
      ];
    }
    if (!empty($settings['vid']) && ($variant = GridStackVariant::load($settings['vid']))) {
      if ($uri = $variant
        ->getIconUri()) {
        $icons['base']['#suffix'] = $this
          ->t('Original layout');
        $icons['variant'] = [
          '#theme' => 'image',
          '#uri' => $uri,
          '#alt' => $this
            ->t('Thumbnail'),
          '#suffix' => $this
            ->t('Variant @label', [
            '@label' => $variant
              ->label(),
          ]),
        ];
        $element['global']['icon']['#attributes']['class'][] = 'form-wrapper--icon';
      }
    }
    $element['global']['icon']['#items'] = $icons;

    // GridStack variant ID dynamically changed based on selection.
    $element['global']['vid'] = [
      '#type' => 'hidden',
      '#default_value' => empty($settings['vid']) ? $vid : $settings['vid'],
    ];

    // GridStack unique ID for the current layout which can be many on a page.
    $element['global']['gid'] = [
      '#type' => 'hidden',
      '#default_value' => empty($settings['gid']) ? $optionset
        ->id() . ':' . $optionset
        ->randomize(4) : $settings['gid'],
    ];

    // Provides entity-related metadata.
    $entity = $extras;
    unset($entity['entity'], $entity['field_options']);
    $element['global']['metadata'] = [
      '#type' => 'hidden',
      '#default_value' => $extras ? Json::encode($entity) : '',
    ];
    return $element;
  }

  /**
   * {@inheritdoc}
   */
  protected function wrapperForm($optionset, FormStateInterface $form_state, array $settings, array $extras = []) {
    $element = [];
    $context = $settings['_scope'];
    $desc = $context == GridStackDefault::ROOT ? $this
      ->t('Affecting all. To refine, use region settings.') : '';
    $element['wrappers'] = [
      '#type' => 'details',
      '#open' => FALSE,
      '#tree' => FALSE,
      '#title' => $this
        ->t('Wrappers'),
      '#attributes' => [
        'class' => [
          'form-wrapper--wrappers',
        ],
      ],
    ];
    $element['wrappers'] = array_merge($element['wrappers'], $this
      ->wrapperElement($optionset, $form_state, $settings, $extras));
    $element['preset_classes'] = [
      '#type' => 'details',
      '#open' => FALSE,
      '#tree' => TRUE,
      '#title' => $this
        ->t('Generic preset classes'),
      '#description' => $this
        ->t('Merged with <code>Classes</code> once saved. Leave any color empty for <code>Styles</code> to work.') . ' ' . $desc,
      '#attributes' => [
        'class' => [
          'form-wrapper--preset-classes',
        ],
      ],
      '#region' => $context,
    ];
    $element['preset_classes'] = array_merge($element['preset_classes'], $this
      ->classesElement($optionset, $form_state, $settings, $extras));
    $element['preset_row_classes'] = [
      '#type' => 'details',
      '#open' => FALSE,
      '#tree' => TRUE,
      '#title' => $this
        ->t('Row preset classes'),
      '#description' => $this
        ->t('Merged with <code>Row classes</code> once saved.') . ' ' . $desc,
      '#attributes' => [
        'class' => [
          'form-wrapper--preset-classes',
        ],
      ],
      '#region' => $context,
      '#access' => $optionset
        ->isFramework() && !empty($settings['_container']),
    ];
    $element['preset_row_classes'] = array_merge($element['preset_row_classes'], $this
      ->rowClassesElement($optionset, $form_state, $settings, $extras));
    return $element;
  }

  /**
   * {@inheritdoc}
   */
  protected function styleForm($optionset, FormStateInterface $form_state, array $settings, array $extras = []) {
    $element = [];
    $styles = $settings;
    $context = $settings['_scope'];
    foreach (GridStackDefault::styleSettings() as $key => $value) {
      $default = isset($settings['styles'][$key]) ? $settings['styles'][$key] : $value;
      if ($context == GridStackDefault::ROOT) {
        $name = [
          'settings',
          'styles',
          $key,
        ];
      }
      else {
        $name = [
          'regions',
          $context,
          'styles',
          $key,
        ];
      }
      $styles[$key] = $form_state
        ->getValue($name, $default);
    }
    $element['styles'] = [
      '#type' => 'details',
      '#tree' => TRUE,
      '#title' => $this
        ->t('Styles'),
    ];
    $exists = $this->manager
      ->getModuleHandler()
      ->moduleExists('media_library');
    if ($exists && !empty($settings['field_name'])) {
      $styles['field_name'] = $settings['field_name'];
      if ($options = $this
        ->getResponsiveImageOptions()) {
        $value = isset($styles['responsive_image_style']) ? $styles['responsive_image_style'] : '';
        $element['styles']['responsive_image_style'] = [
          '#type' => 'select',
          '#title' => $this
            ->t('Responsive Image'),
          '#options' => $options,
          '#empty_option' => $this
            ->t('- None -'),
          '#default_value' => $value,
          '#wrapper_attributes' => [
            'class' => [
              'form-item--resimage',
            ],
          ],
        ];
      }
      if (!empty($settings['access_media'])) {
        $this
          ->mediaElement($element['styles'], $optionset, $form_state, $styles, $extras);
      }
    }

    // Provides ranges and colors.
    $element['styles'] = array_merge($element['styles'], $this
      ->rangeElement($styles));
    $element['styles']['colors'] = [];
    $element['styles']['colors'] = array_merge($element['styles']['colors'], $this
      ->colorElement($optionset, $form_state, $styles, $extras));

    // Provides extras.
    $element['styles']['extras'] = [];
    $element['styles']['extras'] = array_merge($element['styles']['extras'], $this
      ->extrasElement($optionset, $form_state, $styles, $extras));

    // Provides extras.
    if (!$this
      ->config('animationless')) {
      $element['styles']['animations'] = [];
      $element['styles']['animations'] = array_merge($element['styles']['animations'], $this
        ->animationElement($optionset, $form_state, $styles, $extras));
    }
    return $element;
  }

  /**
   * Provides color palettes.
   */
  private function getColorPalettes() {
    $colors = [];
    if ($palettes = $this
      ->config('palettes')) {
      $palettes = array_map('trim', explode("\n", $palettes));
      foreach ($palettes as $palette) {
        if (strpos($palette, '|') !== FALSE) {
          list($group, $group_color) = array_pad(array_map('trim', explode("|", $palette, 2)), 2, NULL);
          $group_colors = array_map('trim', explode(" ", $group_color));
          $colors[$group] = array_unique($group_colors);
        }
      }
    }
    return $colors;
  }

}

Classes

Namesort descending Description
Form Provides the form elements.