Manipulating Nested Paragraphs in Drupal 8 to implement Owl Carousel

Paragraphs are one of the powerful module in Drupal paradigm, it supports nesting feature and empowers developer with the most dynamic and flexible experience. I have used nested Paragraphs to build an Owl Carousel, where outer Paragraph is used as Carousel Container and the Inner Paragraph is used as Carousel Cards.

I have tried to implement two type of Owl Carousel, One Wide Image Carousel and Multiple Image Card Slider Carousel, both were implemented using same nested Paragraph distinguished with a custom field called field_carousel_type.

In the very beginning, get the Owl Carousel CSS and JS file and attach it through custom theme's libraries.yml file, the content of your custom theme's [MYTHEME].libraries.yml file may look like 

global-styling:
  css:
    theme:
      css/main.css: {}
      css/owl.carousel.min.css: {}
      css/owl.theme.default.min.css: {}
      "//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css": {type: external, minified: true}
 
  js:
    js/jquery-ui.min.js: {}
    js/owl.carousel.min.js: {}
    js/my-custom.js: {}
  dependencies:
    - core/jquery

The next step is to create a paragraph type "carousel", this will act as a Carousel Container and and will have a custom select field "field_carousel_type" this will display carousel based on selected type, and it will have an entity reference field to another paragraph type "carousel_card", this paragraph type have an Imagefield, URL field.

This is the preprocess function for the paragraph used as carousel container.

function [YOURTHEME]_preprocess_paragraph__carousel(&$variables) {
  $paragraph = $variables['paragraph'];

  $type = $paragraph->get("field_carousel_type");
  $type = $type->value;
  $variables['type'] = $type;
}

This is the template file code for paragraph used as carousel container, you can clearly see that "field_carousel_type" value has been used to distinguish a carousel based on its type i.e. owl-carousel--{{ type }} 

If the carousel type is "hero" it will have a class owl-carousel--hero and if a carousel type is "featured" it will have a class owl-carousel--featured

{% block paragraph %}

<div class="rc rc--full">
  <div class="owl-carousel owl-carousel--{{ type }} owl-theme">
    {{ content }}
  </div>
</div>

{% endblock paragraph %}

Next we have to adjust carousel card paragraph type by custom handling through preprocess function in [MYTHEME].theme file. The custom handling is required because there is a slight difference between two type of carousel.

function [YOURTHEME]_preprocess_paragraph__carousel_card(&$variables) {
  $paragraph = $variables['paragraph'];
  $parent_paragraph = $paragraph->getParentEntity();
  $carousel_type = $parent_paragraph->get("field_carousel_type");
  $featured = FALSE;
  if ($carousel_type == "featured") {
    $featured = TRUE;
  }
  $variables['featured'] = $featured;

  $cta = $paragraph->get("field_cta");
  $cta = $cta->getValue();
  if ($cta) {
    $url = Url::fromUri($cta[0]['uri'])->toString();
  }
  $variables['url'] = $url;
}

This is the template file for paragraph carousel_card.

{% block paragraph %}
<div>
  <a href="{{ url }}" title="">
    {% if featured %}
      {{ content|without('field_cta') }}
    {% else %}
    <article class="slide-content">
      {{ content|without('field_cta') }}
    </article>
    {% endif %}
  </a>
</div>
{% endblock paragraph %}

Finally, when all this has been setup, we need to trigger the different carousel in our custom JS file, which has been attached in [MYTHEME].libraries.yml file. 

/**
 * @file
 * MYTHEME Custom JS File, Here Site Custom JS code resides.
 */
(function ($) {
$( document ).ready(function() {
   
    // owl carousel
    $(".owl-carousel--hero").owlCarousel({
        items: 1,
    });

    var fixOwlCarousel = function() {
        stage = $('.owl-stage');
        stage.width(stage.width() * 2);
    }

    var refreshFeatured = function() {
        $(".owl-carousel--featured").trigger('refresh.owl.carousel');
    }

    var featured_carousel = $(".owl-carousel--featured").owlCarousel({
        stagePadding: 100,
        autoWidth: true,
        loop: true,
        margin:30,
        dots: false,
        center: true,
        nav:true,
        onInitialized: fixOwlCarousel,
        onRefreshed: fixOwlCarousel,
        onResized: refreshFeatured,
        navText : ['<img src="/themes/custom/mytheme/img/ui/btn-previous.png">','<img src="/themes/custom/mytheme/img/ui/btn-next.png">']
    });

    var gallery_carousel = $(".owl-carousel--gallery").owlCarousel({
        autoWidth: true,
        nav:true,
        loop: false,
        margin: 30,
        dots: false,
        items: 2,
        touchDrag: false,
        mouseDrag: false,
        onInitialized: fixOwlCarousel,
        onRefreshed: fixOwlCarousel,
        navText : ['<i class="fa fa-angle-left" aria-hidden="true"></i>','<i class="fa fa-angle-right" aria-hidden="true"></i>']
    });

    $(".owl-carousel--gallery .owl-item img").click(function( e ) {
        e.preventDefault();
        var src = $(this).data("img-full");
        $("#gallery__img-selected").fadeOut(400,function(){
            $(this).fadeIn(400)[0].src = src;
        });
    });
});

})(jQuery);