1

I want to attach multiple images to custom entity (commerce product), and display them as follow: 1 large image, and list of thumbnails, user can click on thumbnail thus changing large image.

I found and plan to use the library http://www.elevateweb.co.uk/image-zoom/examples#gallery-lightbox

So my steps:

  1. create subtheme of bootstrap theme
  2. add field "images" to the commerce product entity
  3. write twig file to display images

Is it correct path? or maybe there is better solution? better library?

With image zoom, I need to get output like this

<img id="img_01" src="small/image1.jpg" data-zoom-image="large/image1.jpg"/>

<div id="gal1">

  <a href="#" data-image="small/image1.jpg" data-zoom-image="large/image1.jpg">
    <img id="img_01" src="thumb/image1.jpg" />
  </a>

  <a href="#" data-image="small/image2.jpg" data-zoom-image="large/image2.jpg">
    <img id="img_01" src="thumb/image2.jpg" />
  </a>

</div>
2
  • I would create custom field formatter instead.
    – user21641
    Commented Feb 24, 2016 at 9:40
  • Thanks, but i cant see whole architecture with custom formatter, can you help me - write basic steps with formatter? 1. I make formater plugin extends ImageFormatter 2. override viewElements function? 3. set $element['#theme'] to my own template name? so i just get my own twig template in use. why not just override base ImageFormatter-myfield.twig
    – Vovka
    Commented Feb 24, 2016 at 11:01

1 Answer 1

3
<?php

/**
 * @file
 * Contains \Drupal\mymodule\Plugin\Field\FieldFormatter\SlickMyentityGalleryFormatter.
 */

namespace Drupal\mymodule\Plugin\Field\FieldFormatter;

use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Component\Utility\Html;

/**
 * @FieldFormatter(
 *   id = "slick_myentity_gallery",
 *   label = @Translation("Slick Myentity Gallery"),
 *   field_types = {
 *     "image"
 *   }
 * )
 */
class SlickMyentityGalleryFormatter extends FormatterBase {

  /**
   * {@inheritdoc}
   */
  public static function defaultSettings() {
    return [
      'visible_slides' => 3,
      'image_style' => 'thumbnail',
      'arrows' => TRUE,
      'dots' => TRUE,
      'fade' => FALSE,
      'standalone_image' => TRUE,
      'standalone_location' => 'above',
      'standalone_style' => 'xlarge',
      'vertical' => FALSE,
      'lazy_load' => 'ondemand',
      'hide_single_navigator' => TRUE
    ] + parent::defaultSettings();
  }

  /**
   * {@inheritdoc}
   */
  public function settingsForm(array $form, FormStateInterface $form_state) {
    $element = parent::settingsForm($form, $form_state);
    $field_name = $this->fieldDefinition->getName();

    $image_style_entities = \Drupal::entityManager()
      ->getStorage('image_style')
      ->loadMultiple();

    $image_styles = array_map(function($image_style) {
      return $image_style->label();
    }, $image_style_entities);

    $image_styles = ['_original' => '- ' . $this->t('Original') . ' -'] + $image_styles;

    $element['visible_slides'] = [
      '#type' => 'number',
      '#title' => $this->t('Number of visible slides'),
      '#default_value' => $this->getSetting('visible_slides'),
      '#min' => 1,
      '#max' => 10,
      '#description' => $this->t('Number of images visible at once.')
    ];

    $element['image_style'] = [
      '#type' => 'select',
      '#title' => $this->t('Image style'),
      '#default_value' => $this->getSetting('image_style'),
      '#options' => $image_styles,
      '#description' => $this->t('Image style to display the images with.')
    ];

    $element['arrows'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Show arrows'),
      '#default_value' => $this->getSetting('arrows'),
      '#description' => $this->t('Display the navigation arrows.')
    ];

    $element['dots'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Paging'),
      '#default_value' => $this->getSetting('dots'),
      '#description' => $this->t('Display the paging dots.')
    ];

    $element['fade'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Fade'),
      '#default_value' => $this->getSetting('fade'),
      '#description' => $this->t('Fade between transitions.')
    ];

    $element['vertical'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Vertical slider'),
      '#default_value' => $this->getSetting('vertical'),
      '#description' => $this->t('Display slider vertically instead of horizontally.')
    ];

    $element['lazy_load'] = [
      '#type' => 'select',
      '#title' => $this->t('Lazy load'),
      '#options' => [
        'none' => '- ' . $this->t('None') . ' -',
        'ondemand' => $this->t('On demand'),
        'progressive' => $this->t('Progressive')
      ],
      '#default_value' => $this->getSetting('lazy_load'),
      '#description' => $this->t('Lazy load the images to increase page performance.')
    ];

    $element['standalone_image'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Standalone active image'),
      '#default_value' => $this->getSetting('standalone_image'),
      '#description' => $this->t('Display the active image separately.')
    ];

    $element['standalone_style'] = [
      '#type' => 'select',
      '#title' => $this->t('Standalone image style'),
      '#default_value' => $this->getSetting('standalone_style'),
      '#options' => $image_styles,
      '#description' => $this->t('Image style to display the standalone image with.'),
      '#states' => [
        'visible' => [
          "input[name=\"fields[$field_name][settings_edit_form][settings][standalone_image]\"]" => [
            'checked' => TRUE
          ]
        ]
      ]
    ];

    $element['standalone_location'] = [
      '#type' => 'select',
      '#title' => $this->t('Standalone image location'),
      '#default_value' => $this->getSetting('standalone_location'),
      '#options' => [
        'above' => $this->t('Above'),
        'below' => $this->t('Below'),
        'left' => $this->t('Left'),
        'right' => $this->t('Right')
      ],
      '#description' => $this->t('Display slider vertically instead of horizontally.'),
      '#states' => [
        'visible' => [
          "input[name=\"fields[$field_name][settings_edit_form][settings][standalone_image]\"]" => [
            'checked' => TRUE
          ]
        ]
      ]
    ];

    $element['hide_single_navigator'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Hide single item carousel'),
      '#default_value' => $this->getSetting('hide_single_navigator'),
      '#description' => $this->t('If standalone image is displayed and there is only one image, the carousel will be hidden.'),
      '#states' => [
        'visible' => [
          "input[name=\"fields[$field_name][settings_edit_form][settings][standalone_image]\"]" => [
            'checked' => TRUE
          ]
        ]
      ]
    ];

    return $element;
  }

  /**
   * {@inheritdoc}
   */
  public function settingsSummary() {
    $summary = parent::settingsSummary();

    if ($this->getSetting('standalone_image')) {
      $standalone_style = $this->getSetting('standalone_style');
      if ($standalone_style == '_original') {
        $summary[] = $this->t('Display standalone image without image style.');
      } else {
        $style = \Drupal::entityManager()->getStorage('image_style')->load($standalone_style);
        $summary[] = $this->t('Display standalone image with %style image style.', [
          '%style' => $style ? $style->label() : 'original'
        ]);
      }

      $location = $this->getSetting('standalone_location');
      $location_labels = [
        'above' => $this->t('above'),
        'below' => $this->t('below'),
        'left' => $this->t('left'),
        'right' => $this->t('right')
      ];
      $summary[] = $this->t('Standalone image position: %position.', [
        '%position' => $location_labels[$location]
      ]);

      if ($this->getSetting('hide_single_navigator')) {
        $summary[] = $this->t('Hide carousel with single image.');
      }
    }

    $image_style = $this->getSetting('image_style');
    if ($image_style == '_original') {
      $summary[] = $this->t('Display images without image style.');
    } else {
      $style = \Drupal::entityManager()->getStorage('image_style')->load($image_style);
      $summary[] = $this->t('Display images with %style image style.', [
        '%style' => $style ? $style->label() : 'original'
      ]);
    }

    if ($this->getSetting('vertical')) {
      $summary[] = $this->t('Display slider vertically.');
    }

    switch ($this->getSetting('lazy_load')) {
      case 'ondemand':  $summary[] = $this->t('Use <em>on demand</em> lazy loading.'); break;
      case 'progressive':  $summary[] = $this->t('Use <em>progressive</em> lazy loading.'); break;
    }

    return $summary;
  }

  /**
   * {@inheritdoc}
   */
  public function viewElements(FieldItemListInterface $items, $langcode) {
    // Skip if there are no items to display.
    if ($items->isEmpty()) {
      return [];
    }

    $standalone_html_id = Html::getUniqueId($items->getName() . '-standalone');
    $carousel_id = Html::getUniqueId($items->getName() . '-nav');
    $lazy_load = $this->getSetting('lazy_load');
    $use_lazy_load = $lazy_load != 'none';

    // Retrieve file IDs.
    $fids = [];
    foreach ($items->getIterator() AS $item) {
      $fids[] = $item->get('target_id')->getValue();
    }

    // Load files so we can get their URIs.
    $files = \Drupal::entityManager()->getStorage('file')->loadMultiple($fids);

    // Make a list of all URIs.
    $uris = array_map(function($file) {
      return $file->getFileUri();
    }, $files);

    // Set up the render element.
    $element = [
      '#attached' => [
        'library' => [
          'mymodule/slick_myentity_gallery'
        ]
      ]
    ];

    // Create carousel for the standalone image if needed.
    if ($this->getSetting('standalone_image')) {
      $element['#attributes']['class'][] = 'with-standalone';

      $element['standalone'] = [
        '#theme' => 'item_list',
        '#items' => [],
        '#weight' => 0,
        '#attributes' => [
          'id' => $standalone_html_id,
          'class' => [
            'slick-myentity-gallery',
            'slider-standalone',
            'location-' . $this->getSetting('standalone_location')
          ],
          'data-slick-settings' => json_encode([
            'slidesToShow' => 1,
            'slidesToScroll' => 1,
            'arrows' => FALSE,
            'fade' =>  TRUE,
            'asNavFor' => '#' . $carousel_id,
            'lazyLoad' => $lazy_load,
            'mobileFirst' => TRUE
          ])
        ]
      ];

      foreach ($uris AS $uri) {
        $element['standalone']['#items'][] = $this->getImageItem($uri, $this->getSetting('standalone_style'), $use_lazy_load);
      }
    }

    // Create the main image carousel.
    $element['carousel'] = [
      '#theme' => 'item_list',
      '#items' => [],
      '#weight' => 1,
      '#attributes' => [
        'id' => $carousel_id,
        'class' => [
          'slick-myentity-gallery',
          'slider-navigation',
          $this->getSetting('vertical') ? 'vertical' : 'horizontal'
        ],
        'data-slick-settings' => json_encode([
          'slidesToShow' => (int) $this->getSetting('visible_slides'),
          'slidesToScroll' => 1,
          'asNavFor' => '#' . $standalone_html_id,
          'dots' => (bool) $this->getSetting('dots'),
          'arrows' => (bool) $this->getSetting('arrows'),
          'fade' => (bool) $this->getSetting('fade'),
          'vertical' => (bool) $this->getSetting('vertical'),
          'verticalSwiping' => (bool) $this->getSetting('vertical'),
          'focusOnSelect' => TRUE,
          'lazyLoad' => $lazy_load,
          'mobileFirst' => TRUE,
          'rtl' => \Drupal::languageManager()->getCurrentLanguage()->getDirection() == 'rtl',
          'infinite' => FALSE
        ])
      ]
    ];

    foreach ($uris AS $uri) {
      $element['carousel']['#items'][] = $this->getImageItem($uri, $this->getSetting('image_style'), $use_lazy_load);
    }

    // Display the carouse first if the standalone image is visible and
    // is positioned below the carousel or to the right next to it.
    if ($this->getSetting('standalone_image') && in_array($this->getSetting('standalone_location'), ['below', 'right'])) {
      $element['carousel']['#weight'] = -1;
    }

    // Hide navigator if standalone image is displayed and there is only
    // one image.
    if ($this->getSetting('standalone_image') && $this->getSetting('hide_single_navigator') && $items->count() == 1) {
      unset($element['carousel']);
    }

    return [$element];
  }

  /**
   * Helper method to generate image for provided style
   * that takes the lazy load setting into account.
   *
   * @param string $uri
   *   The original image uri.
   * @param string $image_style
   *   The image style name.
   * @param bool $lazy_load
   *   If TRUE the lazy slick load image will be returned.
   *
   * @return array
   *   Renderable array for a single image.
   */
  function getImageItem($uri, $image_style, $lazy_load = FALSE) {
    if ($lazy_load) {
      $style = \Drupal::entityManager()
        ->getStorage('image_style')
        ->load($image_style);

      return [
        '#type' => 'inline_template',
        '#template' => '<img data-lazy="{{ path }}" />',
        '#context' => [
          'path' => $style ? $style->buildUrl($uri) : file_create_url($uri)
        ]
      ];
    } else {
      return [
        '#theme' => 'image_style',
        '#uri' => $uri,
        '#style_name' => $image_style
      ];
    }
  }

}


(function($) {

  Drupal.behaviors.slickMyentityGallery = {
    attach: function(context) {
      // Initialize the Slick Carousel on all appropriate elements.
      $('.slick-myentity-gallery[data-slick-settings]', context).once('slick-myentity-gallery-init').each(function() {
        var $this = $(this);
        var settings = $this.data('slick-settings');

        // Adjust behavior if this is a Field API field.
        var $fapi_items = $this.find('.field__item');
        if ($fapi_items.length != 0) {
          $fapi_items.parent().slick( settings );
        } else {
          $this.slick( settings );
        }
      });
    }
  }

})(jQuery);
1
  • Would you please provide full custom module?
    – user27114
    Commented Jul 22, 2016 at 13:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.