vvjs-1.0.1/js/vvjs-main.js

js/vvjs-main.js
/**
 * @file
 * Main Views Vanilla JavaScript Slideshow orchestrator.
 *
 * Coordinates all slideshow modules and provides the main Drupal behavior.
 *
 * Filename:     vvjs-main.js
 * Website:      https://www.flashwebcenter.com
 * Developer:    Alaa Haddad https://www.alaahaddad.com.
 */

((Drupal, drupalSettings, once) => {
  'use strict';

  /**
   * Main slideshow orchestrator class.
   */
  class VVJSSlideshow {
    constructor(container) {
      this.container = container;
      this.modules = {};

      // Validate required modules are available
      if (!this.validateDependencies()) {
        console.error('VVJS: Required modules not loaded');
        return;
      }

      this.init();
    }

    /**
     * Validate that all required modules are loaded.
     */
    validateDependencies() {
      const requiredModules = [
        'SlideshowCore',
        'SlideshowTransitions',
        'SlideshowNavigation',
        'SlideshowAccessibility',
        'SlideshowProgress',
        'SlideshowVisibility',
        'SlideshowEvents'
      ];

      return requiredModules.every(module =>
        Drupal.vvjs && typeof Drupal.vvjs[module] === 'function'
      );
    }

    /**
     * Initialize all slideshow modules.
     */
    init() {
      try {
        // Initialize core first
        this.modules.core = new Drupal.vvjs.SlideshowCore(this.container);

        // Initialize transitions module second (handles visual transitions)
        this.modules.transitions = new Drupal.vvjs.SlideshowTransitions(
          this.container,
          this.modules.core
        );

        // Initialize dependent modules
        this.modules.navigation = new Drupal.vvjs.SlideshowNavigation(
          this.container,
          this.modules.core
        );

        this.modules.accessibility = new Drupal.vvjs.SlideshowAccessibility(
          this.container,
          this.modules.core
        );

        this.modules.progress = new Drupal.vvjs.SlideshowProgress(
          this.container,
          this.modules.core
        );

        this.modules.visibility = new Drupal.vvjs.SlideshowVisibility(
          this.container,
          this.modules.core
        );

        this.modules.events = new Drupal.vvjs.SlideshowEvents(
          this.container,
          this.modules.core
        );

        // IMPORTANT: Store module references on container for cross-module communication
        this.container.vvjsModules = this.modules;

        // Initialize deep linking if enabled - may activate a slide from URL
        const deepLinkActivated = this.initializeDeepLinking();

        // Start the slideshow
        this.modules.core.startAutoSlide();

        // Mark as initialized
        this.container.classList.add('vvjs-initialized');

        // Dispatch initialization event
        this.container.dispatchEvent(new CustomEvent('vvjs:initialized', {
          detail: { slideshow: this }
        }));

      } catch (error) {
        console.error('VVJS: Initialization error', error);
        this.handleInitializationError(error);
      }
    }

    /**
     * Handle initialization errors gracefully.
     */
    handleInitializationError(error) {
      // Add error class for CSS styling
      this.container.classList.add('vvjs-error');

      // Try to provide basic functionality even if some modules fail
      if (this.modules.core) {
        // At least try to show the first slide
        this.modules.core.updateSlideVisibility();
      }

      // Log error for debugging
      console.error('VVJS initialization failed:', error);
    }

    /**
     * Get reference to specific module.
     */
    getModule(moduleName) {
      return this.modules[moduleName] || null;
    }

    /**
     * Get all module references.
     */
    getAllModules() {
      return { ...this.modules };
    }

    /**
     * Check if slideshow is properly initialized.
     */
    isInitialized() {
      return this.container.classList.contains('vvjs-initialized');
    }

    /**
     * Reinitialize slideshow (useful for dynamic content changes).
     */
    reinitialize() {
      this.destroy();
      this.init();
    }

    /**
     * Destroy slideshow and cleanup all modules.
     */
    destroy() {
      // Destroy modules in reverse order
      Object.keys(this.modules).reverse().forEach(moduleName => {
        const module = this.modules[moduleName];
        if (module && typeof module.destroy === 'function') {
          try {
            module.destroy();
          } catch (error) {
            console.error(`Error destroying ${moduleName} module:`, error);
          }
        }
      });

      // Clear modules
      this.modules = {};

      // Remove classes
      this.container.classList.remove('vvjs-initialized', 'vvjs-error');

      // Dispatch destruction event
      this.container.dispatchEvent(new CustomEvent('vvjs:destroyed'));
    }

    /**
     * Get slideshow configuration and state.
     */
    getState() {
      const core = this.modules.core;
      if (!core) return null;

      return {
        slideIndex: core.slideIndex,
        totalSlides: core.totalSlides,
        isPaused: core.isPaused,
        isVisible: core.isVisible,
        slideTime: core.slideTime,
        modules: Object.keys(this.modules)
      };
    }

    /**
     * Update slideshow configuration.
     */
    updateConfig(config) {
      if (this.modules.core) {
        // Update core configuration
        Object.assign(this.modules.core, config);

        // Notify modules of configuration change
        this.container.dispatchEvent(new CustomEvent('vvjs:configChanged', {
          detail: { config }
        }));
      }
    }

    /**
     * Initialize deep linking functionality.
     * 
     * Checks URL hash on page load and navigates to the specified slide.
     * Also sets up hash change listener for browser back/forward.
     */
    initializeDeepLinking() {
      const deeplinkEnabled = this.container.dataset.deeplinkEnabled === 'true';
      const deeplinkId = this.container.dataset.deeplinkId;

      if (!deeplinkEnabled || !deeplinkId) {
        return false;
      }

      // Check URL hash on page load
      let slideActivated = false;
      const hash = window.location.hash;
      
      if (hash && hash.startsWith(`#${deeplinkId}-`)) {
        const slideNumber = parseInt(hash.split('-').pop(), 10);
        
        if (slideNumber >= 1 && slideNumber <= this.modules.core.totalSlides) {
          // Navigate to the slide from URL
          this.modules.core.goToSlide(slideNumber);
          slideActivated = true;
        }
      }

      // Set up hash change listener (only once per slideshow)
      if (!this.container.vvjsHashChangeHandler) {
        const hashChangeHandler = () => {
          const currentHash = window.location.hash;
          
          // Only respond to hash changes for this specific slideshow
          if (currentHash && currentHash.startsWith(`#${deeplinkId}-`)) {
            const slideNumber = parseInt(currentHash.split('-').pop(), 10);
            
            if (slideNumber >= 1 && slideNumber <= this.modules.core.totalSlides) {
              this.modules.core.goToSlide(slideNumber);
              this.modules.core.startAutoSlide();
            }
          }
        };

        this.container.vvjsHashChangeHandler = hashChangeHandler;
        window.addEventListener('hashchange', hashChangeHandler);
      }

      // Listen for slide changes to update URL hash
      this.container.addEventListener('vvjs:slideChanged', (e) => {
        if (deeplinkEnabled && deeplinkId) {
          const newHash = `#${deeplinkId}-${e.detail.slideIndex}`;
          
          // Use replaceState to avoid cluttering browser history
          if (window.history && window.history.replaceState) {
            window.history.replaceState(null, '', newHash);
          } else {
            window.location.hash = newHash;
          }
        }
      });

      return slideActivated;
    }
  }

  /**
   * Main Drupal behavior for VVJS slideshows.
   */
  Drupal.behaviors.VVJSlideshow = {
    attach(context) {
      // Find all slideshow containers that haven't been initialized
      const slideshows = once('vvjSlideshow', '.vvjs-inner', context);

      if (!slideshows.length) {
        return;
      }

      // Initialize each slideshow
      slideshows.forEach((container) => {
        try {
          // Store slideshow instance on the container for external access
          container.vvjsSlideshow = new VVJSSlideshow(container);
        } catch (error) {
          console.error('Failed to initialize VVJS slideshow:', error);

          // Add error state to container
          container.classList.add('vvjs-init-failed');
        }
      });
    },

    /**
     * Cleanup when elements are removed from DOM.
     */
    detach(context, settings, trigger) {
      if (trigger === 'unload') {
        // Find initialized slideshows and destroy them
        const slideshows = context.querySelectorAll('.vvjs-inner.vvjs-initialized');

        slideshows.forEach((container) => {
          if (container.vvjsSlideshow) {
            container.vvjsSlideshow.destroy();
            delete container.vvjsSlideshow;
          }
        });
      }
    }
  };

  /**
   * Global utility functions for external access.
   */
  Drupal.vvjs = Drupal.vvjs || {};

  /**
   * Get slideshow instance by container element or selector.
   */
  Drupal.vvjs.getInstance = function(containerOrSelector) {
    let container;

    if (typeof containerOrSelector === 'string') {
      container = document.querySelector(containerOrSelector);
    } else {
      container = containerOrSelector;
    }

    return container && container.vvjsSlideshow ? container.vvjsSlideshow : null;
  };

  /**
   * Get all active slideshow instances.
   */
  Drupal.vvjs.getAllInstances = function() {
    const containers = document.querySelectorAll('.vvjs-inner.vvjs-initialized');
    return Array.from(containers)
      .map(container => container.vvjsSlideshow)
      .filter(Boolean);
  };

  /**
   * Pause all slideshows on the page.
   */
  Drupal.vvjs.pauseAll = function() {
    Drupal.vvjs.getAllInstances().forEach(slideshow => {
      const core = slideshow.getModule('core');
      if (core && !core.isPaused) {
        core.togglePause();
      }
    });
  };

  /**
   * Resume all slideshows on the page.
   */
  Drupal.vvjs.resumeAll = function() {
    Drupal.vvjs.getAllInstances().forEach(slideshow => {
      const core = slideshow.getModule('core');
      if (core && core.isPaused) {
        core.togglePause();
      }
    });
  };

/**
   * Helper function to get slideshow container by identifier.
   *
   * @param {string} identifier
   *   The slideshow identifier (deeplink_identifier or CSS selector).
   *
   * @return {HTMLElement|null}
   *   The container element or null if not found.
   */
  function getContainerByIdentifier(identifier) {
    let container;

    // Try deep link identifier first (if not a CSS selector)
    if (!identifier.startsWith('.') && !identifier.startsWith('#')) {
      container = document.querySelector(`[data-deeplink-id="${identifier}"]`);
    }

    // Fallback to CSS selector
    if (!container) {
      container = document.querySelector(identifier);
    }

    return container;
  }

  /**
   * Helper function to get core module from identifier.
   *
   * @param {string} identifier
   *   The slideshow identifier.
   *
   * @return {Object|null}
   *   The core module or null if not found.
   */
  function getCoreModule(identifier) {
    const container = getContainerByIdentifier(identifier);

    if (!container || !container.vvjsSlideshow) {
      return null;
    }

    return container.vvjsSlideshow.getModule('core');
  }

  /**
   * Navigate to a specific slide by identifier.
   *
   * @param {string} identifier
   *   The slideshow identifier (from deeplink_identifier or container selector).
   * @param {number} slideIndex
   *   The slide number to navigate to (1-based).
   *
   * @return {boolean}
   *   True if navigation was successful, false otherwise.
   *
   * @example
   * Drupal.vvjs.goToSlide('gallery', 3);
   */
  Drupal.vvjs.goToSlide = function(identifier, slideIndex) {
    const core = getCoreModule(identifier);

    if (!core) {
      if (typeof console !== 'undefined' && console.warn) {
        console.warn(`VVJS: Slideshow "${identifier}" not found`);
      }
      return false;
    }

    if (slideIndex < 1 || slideIndex > core.totalSlides) {
      if (typeof console !== 'undefined' && console.warn) {
        console.warn(`VVJS: Invalid slide index ${slideIndex}. Must be between 1 and ${core.totalSlides}`);
      }
      return false;
    }

    core.goToSlide(slideIndex);
    core.startAutoSlide();
    return true;
  };

  /**
   * Get the current slide index for a slideshow.
   *
   * @param {string} identifier
   *   The slideshow identifier.
   *
   * @return {number|null}
   *   The current slide index (1-based) or null if not found.
   *
   * @example
   * const currentSlide = Drupal.vvjs.getCurrentSlide('gallery');
   */
  Drupal.vvjs.getCurrentSlide = function(identifier) {
    const core = getCoreModule(identifier);
    return core ? core.slideIndex : null;
  };

  /**
   * Get total number of slides in a slideshow.
   *
   * @param {string} identifier
   *   The slideshow identifier.
   *
   * @return {number|null}
   *   The total number of slides or null if not found.
   *
   * @example
   * const total = Drupal.vvjs.getTotalSlides('gallery');
   */
  Drupal.vvjs.getTotalSlides = function(identifier) {
    const core = getCoreModule(identifier);
    return core ? core.totalSlides : null;
  };

  /**
   * Navigate to next slide.
   *
   * @param {string} identifier
   *   The slideshow identifier.
   *
   * @return {boolean}
   *   True if successful.
   *
   * @example
   * Drupal.vvjs.nextSlide('gallery');
   */
  Drupal.vvjs.nextSlide = function(identifier) {
    const core = getCoreModule(identifier);

    if (core) {
      core.nextSlide();
      core.startAutoSlide();
      return true;
    }

    return false;
  };

  /**
   * Navigate to previous slide.
   *
   * @param {string} identifier
   *   The slideshow identifier.
   *
   * @return {boolean}
   *   True if successful.
   *
   * @example
   * Drupal.vvjs.prevSlide('gallery');
   */
  Drupal.vvjs.prevSlide = function(identifier) {
    const core = getCoreModule(identifier);

    if (core) {
      core.prevSlide();
      core.startAutoSlide();
      return true;
    }

    return false;
  };

  /**
   * Pause a specific slideshow.
   *
   * @param {string} identifier
   *   The slideshow identifier.
   *
   * @return {boolean}
   *   True if successful.
   *
   * @example
   * Drupal.vvjs.pause('gallery');
   */
  Drupal.vvjs.pause = function(identifier) {
    const core = getCoreModule(identifier);

    if (core && !core.isPaused) {
      core.togglePause();
      return true;
    }

    return false;
  };

  /**
   * Resume a specific slideshow.
   *
   * @param {string} identifier
   *   The slideshow identifier.
   *
   * @return {boolean}
   *   True if successful.
   *
   * @example
   * Drupal.vvjs.resume('gallery');
   */
  Drupal.vvjs.resume = function(identifier) {
    const core = getCoreModule(identifier);

    if (core && core.isPaused) {
      core.togglePause();
      return true;
    }

    return false;
  };

})(Drupal, drupalSettings, once);

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

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