vvjs-1.0.1/templates/views-view-vvjs.html.twig

templates/views-view-vvjs.html.twig
{#
/**
 * @file
 * Enhanced theme implementation for Views Vanilla JavaScript Slideshow.
 *
 * Improved with better organization, security fixes, and maintainable structure
 * while preserving 100% backward compatibility and identical HTML output.
 *
 * Available variables:
 * - options: View plugin style options.
 *   - arrows: Display arrows for navigation.
 *   - navigation: Display bottom navigation (dots or numbers).
 *   - animation: Animation type for slide transitions.
 *   - time_in_seconds: Time for each slide.
 *   - hero_slideshow: Enable hero slideshow mode.
 *   - max_width, min_height, max_content_width: Hero layout settings.
 *   - overlay_position: Hero overlay positioning.
 *   - show_total_slides, show_play_pause, show_slide_progress: Display options.
 * - rows: The view result rows to be rendered.
 * - unique_id: A unique identifier for the view instance.
 * - background_rgb: Calculated background color with opacity.
 *
 * @see template_preprocess_views_view_vvjs()
 *
 * @ingroup themeable
 */
#}

{#
  ==============================================================================
  TEMPLATE CONFIGURATION & VARIABLES
  ==============================================================================
#}

{# Core slideshow identifiers - preserved for CSS/JS compatibility #}
{% set slideshow_config = {
  unique_id: options.unique_id|default(0),
  slide_id: 'vvjs-' ~ (options.unique_id|default(0)),
  slide_inner_id: 'vvjs-inner-' ~ (options.unique_id|default(0)),
} %}

{# Navigation and interaction settings #}
{% set navigation_config = {
  arrows: options.arrows|default('none'),
  navigation: options.navigation|default('none'),
  show_total_slides: options.show_total_slides|default(false),
  show_play_pause: options.show_play_pause|default(false),
  show_slide_progress: options.show_slide_progress|default(false),
} %}

{# Timing and animation settings #}
{% set animation_config = {
  time_in_seconds: options.time_in_seconds|default(0),
  animation: options.animation|default(''),
  is_static: (options.time_in_seconds|default(0)) == 0,
} %}

{# Hero slideshow specific settings #}
{% set hero_config = {
  enabled: options.hero_slideshow|default(false),
  max_width: options.max_width|default(1200),
  min_height: options.min_height|default(40),
  max_content_width: options.max_content_width|default(60),
  overlay_position: options.overlay_position|default('d-middle'),
  background_rgb: background_rgb|default(null),
} %}

{# Deep linking configuration #}
{% set deeplink_config = {
  enabled: options.enable_deeplink|default(false),
  identifier: options.deeplink_identifier|default(''),
} %}

{# Transition settings #}
{% set transition_config = {
  type: settings.transition_type|default('instant'),
  duration: settings.transition_duration|default(600),
} %}

{# Calculate total slides once for efficiency #}
{% set total_slides = rows|length %}

{# Build CSS classes array - preserved exact structure for compatibility #}
{% set slideshow_classes = [
  'vvjs',
  'vvjs-' ~ slideshow_config.unique_id,
  navigation_config.arrows == 'none' ? '' : navigation_config.arrows,
  slideshow_config.slide_id,
  animation_config.animation,
  hero_config.enabled ? 'hero-slideshow' : 'slideshow',
  navigation_config.show_slide_progress ? 'slide-progress' : '',
  navigation_config.show_total_slides ? 'total-slides' : '',
  options.available_breakpoints ? 'br-' ~ options.available_breakpoints : '',
] %}

{#
  ==============================================================================
  SLIDESHOW MARKUP MACROS
  ==============================================================================
#}

{# Macro for generating individual hero slideshow items #}
{% macro render_hero_slide(row_content, slideshow_config, hero_config, navigation_config, loop_info, key) %}
  {# Split content into image and content sections - preserved exact logic #}
  {% set split_content = row_content|split('<div class="vvjs-separator"></div>') %}
  {% set hero_image = split_content[0]|default('') %}
  {% set hero_content = split_content[1]|default('') %}

  <div id="vvjs-item-{{ slideshow_config.unique_id }}-{{ loop_info.index }}"
       class="vvjs-item"
       role="tabpanel"
       tabindex="{{ loop_info.first ? '0' : '-1' }}"
       aria-hidden="{{ loop_info.first ? 'false' : 'true' }}"
       {%- if navigation_config.navigation != 'none' %} aria-labelledby="dots-numbers-button-{{ loop_info.index }}"{% endif %}>

    <div class="vvjs-item-inner"
         id="{{ slideshow_config.slide_inner_id }}-{{ loop_info.index }}-pane"
         role="group"
         aria-labelledby="{{ slideshow_config.slide_id }}-image-{{ key + 1 }} {{ slideshow_config.slide_id }}-content-{{ key + 1 }}">

      <div class="vvjs-hero-image"
           role="img"
           aria-labelledby="{{ slideshow_config.slide_id }}-image-{{ key + 1 }}">
        {# SECURITY: Using |raw since content is already rendered by Drupal's system #}
        {{ hero_image|raw }}
      </div>

      <div class="vvjs-hero-content {{ hero_config.overlay_position }}"
           style="{% if hero_config.background_rgb %}--hero-content-bg:{{ hero_config.background_rgb }};{% endif %} --hero-content-width: {{ hero_config.max_content_width }};"
           role="complementary"
           aria-labelledby="{{ slideshow_config.slide_id }}-content-{{ key + 1 }}">
        {# SECURITY: Using |raw since content is already rendered by Drupal's system #}
        {{ hero_content|raw }}
      </div>

    </div>

  </div>
{% endmacro %}

{# Macro for generating regular slideshow items #}
{% macro render_regular_slide(row, slideshow_config, navigation_config, loop_info) %}
  <div id="vvjs-item-{{ slideshow_config.unique_id }}-{{ loop_info.index }}"
       class="vvjs-item"
       role="tabpanel"
       tabindex="{{ loop_info.first ? '0' : '-1' }}"
       aria-hidden="{{ loop_info.first ? 'false' : 'true' }}"
       {%- if navigation_config.navigation != 'none' %} aria-labelledby="dots-numbers-button-{{ loop_info.index }}"{% endif %}>
    <div id="{{ slideshow_config.slide_inner_id }}-{{ loop_info.index }}-pane" class="vvjs-item-inner">
      {{ row.content }}
    </div>
  </div>
{% endmacro %}

{# Macro for navigation dot buttons #}
{% macro render_navigation_dots(rows, slideshow_config, navigation_config, deeplink_config) %}
  {% if navigation_config.navigation != 'none' %}
  <div class="dots-numbers-button-wrapper" role="tablist" aria-label="{{ 'Slideshow Tabs'|t }}">
    {% for row in rows %}
      {% if deeplink_config.enabled and deeplink_config.identifier and navigation_config.navigation != 'none' %}
        {# Deep linking enabled - use anchor links #}
        {% set slide_link = '#' ~ deeplink_config.identifier ~ '-' ~ loop.index %}
        <a id="dots-numbers-button-{{ loop.index }}"
           href="{{ slide_link }}"
           class="button dots-numbers-button{{ loop.first ? ' active' : '' }}"
           role="tab"
           aria-label="{{ loop.first ? 'Slide @index selected'|t({'@index': loop.index}) : 'Go to slide @index'|t({'@index': loop.index}) }}"
           aria-selected="{{ loop.first ? 'true' : 'false' }}"
           aria-controls="vvjs-item-{{ slideshow_config.unique_id }}-{{ loop.index }}"
           tabindex="{{ loop.first ? '0' : '-1' }}">
          {{ loop.index }}
        </a>
      {% else %}
        {# Default buttons #}
        <button id="dots-numbers-button-{{ loop.index }}"
                class="button dots-numbers-button{{ loop.first ? ' active' : '' }}"
                type="button"
                role="tab"
                aria-label="{{ loop.first ? 'Slide @index selected'|t({'@index': loop.index}) : 'Go to slide @index'|t({'@index': loop.index}) }}"
                aria-selected="{{ loop.first ? 'true' : 'false' }}"
                aria-controls="vvjs-item-{{ slideshow_config.unique_id }}-{{ loop.index }}"
                tabindex="{{ loop.first ? '0' : '-1' }}">
          {{ loop.index }}
        </button>
      {% endif %}
    {% endfor %}
  </div>
  {% endif %}
{% endmacro %}

{#
  ==============================================================================
  MAIN SLIDESHOW TEMPLATE
  ==============================================================================
#}

{# Main slideshow wrapper with accessibility and data attributes #}
<div {{ attributes.addClass(slideshow_classes).setAttribute('id', slideshow_config.slide_id) }}
     role="region"
     aria-labelledby="slideshow-heading-{{ slideshow_config.unique_id }}">

  {# Hidden heading for screen readers #}
  <div id="slideshow-heading-{{ slideshow_config.unique_id }}" role="heading" class="visually-hidden">
    {{ 'Slideshow'|t }}
  </div>

  {# Inner container with JavaScript data attributes - preserved exact structure #}
  <div id="{{ slideshow_config.slide_inner_id }}"
       data-transition="{{ transition_config.type }}"
       data-transition-duration="{{ transition_config.duration }}"
       data-arrows="{{ navigation_config.arrows != 'none' ? 'true' : 'false' }}"
       data-navigation="{{ navigation_config.navigation != 'none' ? 'true' : 'false' }}"
       data-show-total-slides="{{ navigation_config.show_total_slides ? 'true' : 'false' }}"
       data-show-slide-progress="{{ navigation_config.show_slide_progress ? 'true' : 'false' }}"
       data-play-pause="{{ navigation_config.show_play_pause ? 'true' : 'false' }}"
       data-static="{{ animation_config.is_static ? 'true' : 'false' }}"
       data-time="{{ animation_config.time_in_seconds }}"
       data-total-slides="{{ total_slides }}"
       {% if deeplink_config.enabled and deeplink_config.identifier and navigation_config.navigation != 'none' %}
       data-deeplink-enabled="true"
       data-deeplink-id="{{ deeplink_config.identifier }}"
       {% endif %}
       class="vvjs-inner{{ navigation_config.navigation ? ' ' ~ navigation_config.navigation }}{{ animation_config.is_static ? ' zero' : ' not-zero' }}">

    {# Live region for accessibility announcements #}
    <div class="announcer visually-hidden" aria-live="polite" aria-atomic="true">
      {{ 'Slide 1 selected'|t }}
    </div>

    {# Slides container with hero-specific styling #}
    <div id="vvjs-items-{{ slideshow_config.unique_id }}"
         class="vvjs-items"
         style="{% if transition_config.type starts with 'crossfade' %}--vvjs-transition-duration: {{ transition_config.duration }}ms; {% endif %}{% if hero_config.enabled %}--hero-max-width: {{ hero_config.max_width }}; --hero-min-height: {{ hero_config.min_height }};{% endif %}">

      {#
        ========================================================================
        SLIDE CONTENT GENERATION
        ========================================================================
      #}

      {% if hero_config.enabled %}
        {# Hero Slideshow Mode - Enhanced image/content layout #}
        {% for key, row in rows %}
          {% set row_content = row.content|render %}
          {{ _self.render_hero_slide(row_content, slideshow_config, hero_config, navigation_config, loop, key) }}
        {% endfor %}

      {% else %}
        {# Regular Slideshow Mode - Standard content display #}
        {% for row in rows %}
          {{ _self.render_regular_slide(row, slideshow_config, navigation_config, loop) }}
        {% endfor %}

      {% endif %}

    </div>

    {#
      ==========================================================================
      NAVIGATION CONTROLS
      ==========================================================================
    #}

    {# Only show navigation when multiple slides exist #}
    {% if total_slides > 1 %}

      {# Bottom navigation panel (play/pause, progress, dots/numbers, counter) #}
      {% if navigation_config.navigation != 'none' or navigation_config.show_total_slides or navigation_config.show_slide_progress or navigation_config.show_play_pause %}
        <div id="nav-dots-numbers-{{ slideshow_config.unique_id }}"
             aria-label="{{ 'Slideshow Tabs'|t }}"
             class="nav-dots-numbers {{ navigation_config.navigation }}">

          {# Play/Pause Button #}
          {% if navigation_config.show_play_pause and animation_config.time_in_seconds > 0 %}
          <button id="play-pause-button-{{ slideshow_config.unique_id }}"
                  type="button"
                  role="button"
                  aria-label="{{ 'Stop automatic slide show'|t }}"
                  class="button play-pause-button play playing">
            <span class="visually-hidden">{{ 'Play and Stop Slideshow'|t }}</span>
            {{ include('@vvjs/svg/svg-pause.svg') }}
          </button>
          {% endif %}

          {# Progress Indicator #}
          {% if navigation_config.show_slide_progress and animation_config.time_in_seconds > 0 %}
          <div class="echo-animation">
            <div class="progressbar"
                 role="progressbar"
                 aria-valuenow="0"
                 aria-valuemin="0"
                 aria-valuemax="{{ animation_config.time_in_seconds }}"
                 aria-label="{{ 'Slideshow progress'|t }}"
                 aria-live="polite"
                 data-total-time="{{ animation_config.time_in_seconds }}"
                 data-current-progress="0">
            </div>
          </div>
          {% endif %}

          {# Navigation Dots/Numbers #}
          {{ _self.render_navigation_dots(rows, slideshow_config, navigation_config, deeplink_config) }}

          {# Slide Counter Display #}
          {% if navigation_config.show_total_slides %}
          <div class="echo-total">
            <span class="current-slide">1</span>
            <span class="vvjs-counter-separator"> {{ 'of'|t }} </span>
            <span class="total-slides">{{ total_slides }}</span>
          </div>
          {% endif %}

        </div>
      {% endif %}

      {# Previous/Next Arrow Navigation #}
      {% if navigation_config.arrows != 'none' %}
        <div id="slide-indicators-{{ slideshow_config.unique_id }}"
             class="slide-indicators"
             role="navigation"
             aria-label="{{ 'Slideshow Navigation'|t }}">

          <button class="button prev-arrow"
                  role="button"
                  aria-controls="vvjs-items-{{ slideshow_config.unique_id }}"
                  aria-label="{{ 'Previous Slide'|t }}">
            <span class="visually-hidden">{{ 'Previous Slide'|t }}</span>
            {{ include('@vvjs/svg/svg-prev.svg') }}
          </button>

          <button class="button next-arrow"
                  role="button"
                  aria-controls="vvjs-items-{{ slideshow_config.unique_id }}"
                  aria-label="{{ 'Next Slide'|t }}">
            <span class="visually-hidden">{{ 'Next Slide'|t }}</span>
            {{ include('@vvjs/svg/svg-next.svg') }}
          </button>

        </div>
      {% endif %}

    {% endif %}

  </div>
</div>

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

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