vvjl-1.0.3/src/Plugin/views/style/ViewsVanillaJavascriptLightbox.php

src/Plugin/views/style/ViewsVanillaJavascriptLightbox.php
<?php

declare(strict_types=1);

namespace Drupal\vvjl\Plugin\views\style;

use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\style\StylePluginBase;
use Drupal\vvjl\VvjlConstants;

/**
 * Style plugin to render items in Lightbox using vanilla JavaScript.
 *
 * @ingroup views_style_plugins
 *
 * @ViewsStyle(
 *   id = "views_vvjl",
 *   title = @Translation("Views Vanilla JavaScript Lightbox"),
 *   help = @Translation("Render items in Lightbox using vanilla JavaScript."),
 *   theme = "views_view_vvjl",
 *   display_types = { "normal" }
 * )
 */
class ViewsVanillaJavascriptLightbox extends StylePluginBase {

  /**
   * Animation type constants.
   */
  public const ANIMATION_NONE = 'none';
  public const ANIMATION_ZOOM = 'a-zoom';
  public const ANIMATION_TOP = 'a-top';
  public const ANIMATION_RIGHT = 'a-right';
  public const ANIMATION_BOTTOM = 'a-bottom';
  public const ANIMATION_LEFT = 'a-left';

  /**
   * Does the style plugin use a row plugin.
   *
   * @var bool
   */
  protected $usesRowPlugin = TRUE;

  /**
   * {@inheritdoc}
   */
  protected $usesRowClass = TRUE;

  /**
   * Cached unique ID for this view display.
   *
   * @var int|null
   */
  protected ?int $cachedUniqueId = NULL;

  /**
   * {@inheritdoc}
   */
  protected function defineOptions(): array {
    $options = parent::defineOptions();
    $options[VvjlConstants::OPTION_UNIQUE_ID] = ['default' => $this->generateUniqueId()];
    $options[VvjlConstants::OPTION_GRID_IMAGE_WIDTH] = ['default' => VvjlConstants::DEFAULT_GRID_IMAGE_WIDTH];
    $options[VvjlConstants::OPTION_GRID_IMAGE_GAP] = ['default' => VvjlConstants::DEFAULT_GRID_IMAGE_GAP];
    $options[VvjlConstants::OPTION_OVERLAY_COLOR] = ['default' => VvjlConstants::DEFAULT_OVERLAY_COLOR];
    $options[VvjlConstants::OPTION_OVERLAY_OPACITY] = ['default' => VvjlConstants::DEFAULT_OVERLAY_OPACITY];
    $options[VvjlConstants::OPTION_DISABLE_OVERLAY] = ['default' => VvjlConstants::DEFAULT_DISABLE_OVERLAY];
    $options[VvjlConstants::OPTION_ANIMATION] = ['default' => VvjlConstants::DEFAULT_ANIMATION];
    return $options;
  }

  /**
   * Gets available animation type options.
   *
   * @return array
   *   Array of animation options with translated labels.
   */
  protected function getAnimationOptions(): array {
    return [
      self::ANIMATION_NONE => $this->t('None'),
      self::ANIMATION_ZOOM => $this->t('Zoom'),
      self::ANIMATION_TOP => $this->t('Slide from Top'),
      self::ANIMATION_RIGHT => $this->t('Slide from Right'),
      self::ANIMATION_BOTTOM => $this->t('Slide from Bottom'),
      self::ANIMATION_LEFT => $this->t('Slide from Left'),
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function buildOptionsForm(&$form, FormStateInterface $form_state): void {
    parent::buildOptionsForm($form, $form_state);

    $this->setDefaultElementWeights($form);
    $this->buildWarningMessage($form);
    $this->buildGridSection($form);
    $this->buildAnimationSection($form);
    $this->buildOverlaySection($form);
    $this->buildTokenDocumentation($form);
  }

  /**
   * Set weights for default Drupal form elements.
   */
  protected function setDefaultElementWeights(array &$form): void {
    $default_elements = [
      'grouping' => -100,
      'row_class' => -90,
      'default_row_class' => -85,
      'uses_fields' => -80,
      'class' => -75,
      'wrapper_class' => -70,
    ];

    foreach ($default_elements as $element_key => $weight) {
      if (isset($form[$element_key])) {
        $form[$element_key]['#weight'] = $weight;
      }
    }
  }

  /**
   * Build warning message section.
   */
  protected function buildWarningMessage(array &$form): void {
    if ($this->view->storage->id() === 'vvjl_example') {
      return;
    }
    $form['warning_message'] = [
      '#type' => 'markup',
      '#markup' => '<div class="messages messages--warning">' . $this->t('Note: The first field must be an image. To see an example, check the vvjl_example view by clicking <a href="/admin/structure/views/view/@view_id" style="display: inline;">here</a> to edit it.', ['@view_id' => VvjlConstants::EXAMPLE_VIEW_ID]) . '</div>',
      '#weight' => -50,
    ];
  }

  /**
   * Build grid configuration section.
   */
  protected function buildGridSection(array &$form): void {
    $form['grid_section'] = [
      '#type' => 'details',
      '#title' => $this->t('Grid Settings'),
      '#open' => TRUE,
      '#weight' => -40,
    ];

    $form['grid_section'][VvjlConstants::OPTION_GRID_IMAGE_WIDTH] = [
      '#type' => 'number',
      '#title' => $this->t('Grid Image Width (px)'),
      '#default_value' => $this->options[VvjlConstants::OPTION_GRID_IMAGE_WIDTH],
      '#description' => $this->t('Set the width of the grid image in pixels.'),
      '#step' => 1,
      '#min' => VvjlConstants::MIN_GRID_IMAGE_WIDTH,
      '#required' => TRUE,
    ];

    $form['grid_section'][VvjlConstants::OPTION_GRID_IMAGE_GAP] = [
      '#type' => 'number',
      '#title' => $this->t('Grid Image Gap (px)'),
      '#default_value' => $this->options[VvjlConstants::OPTION_GRID_IMAGE_GAP],
      '#description' => $this->t('Set the gap between grid images in pixels.'),
      '#step' => 1,
      '#min' => VvjlConstants::MIN_GRID_IMAGE_GAP,
      '#required' => TRUE,
    ];
  }

  /**
   * Build animation configuration section.
   */
  protected function buildAnimationSection(array &$form): void {
    $form['animation_section'] = [
      '#type' => 'details',
      '#title' => $this->t('Animation Settings'),
      '#open' => TRUE,
      '#weight' => -30,
    ];

    $form['animation_section'][VvjlConstants::OPTION_ANIMATION] = [
      '#type' => 'select',
      '#title' => $this->t('Animation Type'),
      '#options' => $this->getAnimationOptions(),
      '#default_value' => $this->options[VvjlConstants::OPTION_ANIMATION],
      '#description' => $this->t('Choose the animation type to apply when click next/prev in the modal.'),
    ];
  }

  /**
   * Build overlay configuration section.
   */
  protected function buildOverlaySection(array &$form): void {
    $form['overlay_section'] = [
      '#type' => 'details',
      '#title' => $this->t('Overlay Settings'),
      '#open' => TRUE,
      '#weight' => -20,
    ];

    $form['overlay_section'][VvjlConstants::OPTION_OVERLAY_COLOR] = [
      '#type' => 'color',
      '#title' => $this->t('Overlay Color'),
      '#default_value' => $this->options[VvjlConstants::OPTION_OVERLAY_COLOR],
      '#description' => $this->t('Choose a color overlay for the lightbox background.'),
      '#states' => [
        'disabled' => [
          ':input[name="style_options[overlay_section][' . VvjlConstants::OPTION_DISABLE_OVERLAY . ']"]' => ['checked' => TRUE],
        ],
      ],
    ];

    $form['overlay_section'][VvjlConstants::OPTION_OVERLAY_OPACITY] = [
      '#type' => 'range',
      '#title' => $this->t('Overlay Opacity'),
      '#default_value' => $this->options[VvjlConstants::OPTION_OVERLAY_OPACITY],
      '#min' => VvjlConstants::MIN_OPACITY,
      '#max' => VvjlConstants::MAX_OPACITY,
      '#step' => VvjlConstants::OPACITY_STEP,
      '#description' => $this->t('Set the opacity level for the overlay.'),
      '#suffix' => '<span id="overlay-opacity-value">' . $this->options[VvjlConstants::OPTION_OVERLAY_OPACITY] . '</span>',
      '#attributes' => [
        'oninput' => 'document.getElementById("overlay-opacity-value").innerText = this.value;',
      ],
      '#states' => [
        'disabled' => [
          ':input[name="style_options[overlay_section][' . VvjlConstants::OPTION_DISABLE_OVERLAY . ']"]' => ['checked' => TRUE],
        ],
      ],
    ];

    $form['overlay_section'][VvjlConstants::OPTION_DISABLE_OVERLAY] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Disable Overlay Color'),
      '#default_value' => $this->options[VvjlConstants::OPTION_DISABLE_OVERLAY],
      '#description' => $this->t('Check this to disable overlay color completely.'),
    ];
  }

  /**
   * Build token documentation section.
   */
  protected function buildTokenDocumentation(array &$form): void {
    $form['token_section'] = [
      '#type' => 'details',
      '#title' => $this->t('Token Documentation'),
      '#open' => FALSE,
      '#weight' => 100,
    ];

    $form['token_section']['description'] = [
      '#markup' => $this->t('<p>When using <em>Global: Text area</em> or <em>Global: Unfiltered text</em> in the Views header, footer, or empty text areas, the default Twig-style tokens (e.g., <code>{{ title }}</code>) will not work with the VVJL style plugin.</p>
        <p>Instead, use the custom VVJL token format to access field values from the <strong>first row</strong> of the View result:</p>
        <ul>
          <li><code>[vvjl:field_name]</code> – The rendered output of the field (e.g., linked title, image, formatted text).</li>
          <li><code>[vvjl:field_name:plain]</code> – A plain-text version of the field, with all HTML stripped.</li>
        </ul>
        <p>Examples:</p>
        <ul>
          <li><code>{{ title }}</code> ➜ <code>[vvjl:title]</code></li>
          <li><code>{{ field_image }}</code> ➜ <code>[vvjl:field_image]</code></li>
          <li><code>{{ body }}</code> ➜ <code>[vvjl:body:plain]</code></li>
        </ul>
        <p>These tokens offer safe and flexible field output for dynamic headings, summaries, and fallback messages in VVJL-enabled Views.</p>'),
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function submitOptionsForm(&$form, FormStateInterface $form_state): void {
    $values = $form_state->getValue('style_options');
    $flattened_values = $this->flattenFormValues($values);
    $form_state->setValue('style_options', $flattened_values);

    parent::submitOptionsForm($form, $form_state);
  }

  /**
   * Flatten nested form values to match original structure.
   */
  protected function flattenFormValues(array $values): array {
    $flattened = [];

    if (isset($values['grid_section'])) {
      $flattened[VvjlConstants::OPTION_GRID_IMAGE_WIDTH] = $values['grid_section'][VvjlConstants::OPTION_GRID_IMAGE_WIDTH] ?? VvjlConstants::DEFAULT_GRID_IMAGE_WIDTH;
      $flattened[VvjlConstants::OPTION_GRID_IMAGE_GAP] = $values['grid_section'][VvjlConstants::OPTION_GRID_IMAGE_GAP] ?? VvjlConstants::DEFAULT_GRID_IMAGE_GAP;
    }

    if (isset($values['animation_section'])) {
      $flattened[VvjlConstants::OPTION_ANIMATION] = $values['animation_section'][VvjlConstants::OPTION_ANIMATION] ?? VvjlConstants::DEFAULT_ANIMATION;
    }

    if (isset($values['overlay_section'])) {
      $flattened[VvjlConstants::OPTION_OVERLAY_COLOR] = $values['overlay_section'][VvjlConstants::OPTION_OVERLAY_COLOR] ?? VvjlConstants::DEFAULT_OVERLAY_COLOR;
      $flattened[VvjlConstants::OPTION_OVERLAY_OPACITY] = $values['overlay_section'][VvjlConstants::OPTION_OVERLAY_OPACITY] ?? VvjlConstants::DEFAULT_OVERLAY_OPACITY;
      $flattened[VvjlConstants::OPTION_DISABLE_OVERLAY] = $values['overlay_section'][VvjlConstants::OPTION_DISABLE_OVERLAY] ?? VvjlConstants::DEFAULT_DISABLE_OVERLAY;
    }

    $flattened[VvjlConstants::OPTION_UNIQUE_ID] = $this->options[VvjlConstants::OPTION_UNIQUE_ID] ?? $this->generateUniqueId();

    return $flattened;
  }

  /**
   * Generates a unique numeric ID for the view display.
   *
   * @return int
   *   A unique ID between 10000000 and 99999999.
   *
   * @throws \Random\RandomException
   *   If an appropriate source of randomness cannot be found.
   */
  protected function generateUniqueId(): int {
    if ($this->cachedUniqueId !== NULL) {
      return $this->cachedUniqueId;
    }

    $this->cachedUniqueId = random_int(VvjlConstants::MIN_UNIQUE_ID, VvjlConstants::MAX_UNIQUE_ID);

    return $this->cachedUniqueId;
  }

  /**
   * {@inheritdoc}
   */
  public function validate(): array {
    $errors = parent::validate();

    if (!$this->usesFields()) {
      $errors[] = $this->t('Views Lightbox requires Fields as row style');
    }

    return $errors;
  }

  /**
   * {@inheritdoc}
   */
  public function render(): array {
    $rows = [];

    if (!empty($this->view->result)) {
      foreach ($this->view->result as $row) {
        $rendered_row = $this->view->rowPlugin->render($row);
        if ($rendered_row !== NULL) {
          $rows[] = $rendered_row;
        }
      }
    }

    $libraries = $this->buildLibraryList();

    $build = [
      '#theme' => $this->themeFunctions(),
      '#view' => $this->view,
      '#options' => $this->options,
      '#rows' => $rows,
      '#unique_id' => $this->options[VvjlConstants::OPTION_UNIQUE_ID] ?? $this->generateUniqueId(),
      '#attached' => [
        'library' => $libraries,
      ],
    ];

    return $build;
  }

  /**
   * Build the list of libraries to attach.
   *
   * @return array
   *   An array of library names to attach.
   */
  protected function buildLibraryList(): array {
    return [VvjlConstants::LIBRARY_JS];
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc