Drupal JavaScripts to hide and show resources

{# 1. Main slider paragraph template #}
{# File: templates/paragraph--slider-with-resources.html.twig #}

<div class="slider-with-resources-wrapper">
  <div class="slider-container" data-slick='{"dots": true, "arrows": true, "infinite": true, "speed": 300, "slidesToShow": 1, "slidesToScroll": 1, "responsive": [{"breakpoint": 768, "settings": {"arrows": false}}]}'>
    {% for item in slider_items %}
      {{ drupal_entity('paragraph', item.id(), 'default') }}
    {% endfor %}
  </div>
</div>

{# 2. Slider item template #}
{# File: templates/paragraph--slider-item.html.twig #}

<div class="slider-item">
  <div class="container-fluid">
    <div class="row">
      <div class="col-lg-6 col-md-12">
        {% if content.field_slide_image %}
          <div class="slide-image">
            {{ content.field_slide_image }}
          </div>
        {% endif %}
      </div>
      <div class="col-lg-6 col-md-12">
        <div class="slide-content">
          {% if content.field_slide_title %}
            <h3 class="slide-title">
              {{ content.field_slide_title }}
            </h3>
          {% endif %}
          
          {% if content.field_slide_description %}
            <div class="slide-description">
              {{ content.field_slide_description }}
            </div>
          {% endif %}
          
          {% if resources %}
            <div class="slide-resources">
              <h4>Resources</h4>
              <ul class="resources-list">
                {% for key, resource in resources %}
                  <li class="resource-item{% if key >= 2 %} hidden-resource{% endif %}">
                    <a href="{{ resource.url }}" class="resource-link">
                      {{ resource.title }}
                    </a>
                  </li>
                {% endfor %}
              </ul>
              {% if resources|length > 2 %}
                <button class="btn btn-outline-primary btn-sm show-more-resources" type="button">
                  <span class="show-text">Show 2 More</span>
                  <span class="hide-text d-none">Show Less</span>
                </button>
              {% endif %}
            </div>
          {% endif %}
        </div>
      </div>
    </div>
  </div>
</div>

/**
 * INSTALLATION INSTRUCTIONS
 * 
 * 1. Install required modules:
 *    - Paragraphs module
 *    - Slick module (composer require drupal/slick)
 *    - Slick Views module (usually comes with Slick)
 * 
 * 2. Create the module structure:
 *    modules/custom/your_module/
 *    ├── your_module.info.yml
 *    ├── your_module.module
 *    ├── your_module.libraries.yml
 *    ├── config/install/
 *    │   ├── paragraphs.paragraphs_type.slider_with_resources.yml
 *    │   ├── paragraphs.paragraphs_type.slider_item.yml
 *    │   ├── field.storage.paragraph.field_slider_items.yml
 *    │   ├── field.field.paragraph.slider_with_resources.field_slider_items.yml
 *    │   ├── field.storage.paragraph.field_slide_image.yml
 *    │   ├── field.field.paragraph.slider_item.field_slide_image.yml
 *    │   ├── field.storage.paragraph.field_slide_title.yml
 *    │   ├── field.field.paragraph.slider_item.field_slide_title.yml
 *    │   ├── field.storage.paragraph.field_slide_description.yml
 *    │   ├── field.field.paragraph.slider_item.field_slide_description.yml
 *    │   ├── field.storage.paragraph.field_slide_resources.yml
 *    │   └── field.field.paragraph.slider_item.field_slide_resources.yml
 *    ├── templates/
 *    │   ├── paragraph--slider-with-resources.html.twig
 *    │   └── paragraph--slider-item.html.twig
 *    ├── css/
 *    │   └── slider-resources.css
 *    └── js/
 *        └── slider-resources.js
 * 
 * 3. Module info file (your_module.info.yml):
 *    name: 'Slider with Resources'
 *    type: module
 *    description: 'Custom paragraph with slick slider and expandable resources'
 *    core_version_requirement: ^9 || ^10
 *    dependencies:
 *      - paragraphs:paragraphs
 *      - slick:slick
 * 
 * 4. Field configurations for slider_item:
 * 
 *    Create these fields through the UI or config files:
 *    - field_slide_image: Image field
 *    - field_slide_title: Plain text field
 *    - field_slide_description: Formatted text field
 *    - field_slide_resources: Entity reference field (to taxonomy terms or content)
 *      * Set cardinality to 4
 *      * Target your resource content type or taxonomy
 * 
 * 5. After installation:
 *    - Clear caches
 *    - Go to Structure > Paragraph Types
 *    - Verify both paragraph types exist
 *    - Add the paragraph to your content type
 *    - Configure display settings as needed
 * 
 * USAGE:
 * 1. Add "Slider with Resources" paragraph to any content
 * 2. Add multiple "Slider Item" paragraphs within it
 * 3. Each slider item can have:
 *    - One image
 *    - A title
 *    - Description text
 *    - Up to 4 resource links
 * 4. The first 2 resources show by default
 * 5. "Show 2 More" button reveals the remaining resources
 * 
 * CUSTOMIZATION:
 * - Modify CSS classes to match your Bootstrap theme
 * - Adjust Slick settings in the data-slick attribute
 * - Customize responsive breakpoints in CSS
 * - Change animation effects in JavaScript
 */

<?php
// 3. Paragraph type configuration (add to your module's config/install)
// File: config/install/paragraphs.paragraphs_type.slider_with_resources.yml

/*
langcode: en
status: true
dependencies: {  }
id: slider_with_resources
label: 'Slider with Resources'
icon_uuid: null
icon_default: null
description: 'A slider paragraph with image, title, description and expandable resources'
behavior_plugins: {  }
*/

// 2. Field configuration for the paragraph type
// File: config/install/field.storage.paragraph.field_slider_items.yml

/*
langcode: en
status: true
dependencies:
  module:
    - paragraphs
id: paragraph.field_slider_items
field_name: field_slider_items
entity_type: paragraph
type: entity_reference_revisions
settings:
  target_type: paragraph
module: entity_reference_revisions
locked: false
cardinality: -1
translatable: true
indexes: {  }
persist_with_no_fields: false
custom_storage: false
*/

// 3. Field instance configuration
// File: config/install/field.field.paragraph.slider_with_resources.field_slider_items.yml

/*
langcode: en
status: true
dependencies:
  config:
    - field.storage.paragraph.field_slider_items
    - paragraphs.paragraphs_type.slider_item
    - paragraphs.paragraphs_type.slider_with_resources
  module:
    - entity_reference_revisions
id: paragraph.slider_with_resources.field_slider_items
field_name: field_slider_items
entity_type: paragraph
bundle: slider_with_resources
label: 'Slider Items'
description: ''
required: false
translatable: false
default_value: {  }
default_value_callback: ''
settings:
  handler: 'default:paragraph'
  handler_settings:
    negate: 0
    target_bundles:
      slider_item: slider_item
    target_bundles_drag_drop:
      slider_item:
        enabled: true
        weight: 2
field_type: entity_reference_revisions
*/

// 4. Slider Item paragraph type fields
// Create these field configs for paragraph type 'slider_item':

/*
Fields needed for slider_item paragraph:
- field_slide_image (image)
- field_slide_title (string)  
- field_slide_description (text_long)
- field_slide_resources (entity_reference to taxonomy or content, cardinality: 4)
*/

// 5. Module file with preprocessing
// File: modules/custom/your_module/your_module.module

/**
 * Implements hook_theme().
 */
function your_module_theme($existing, $type, $theme, $path) {
  return [
    'paragraph__slider_with_resources' => [
      'base hook' => 'paragraph',
    ],
    'paragraph__slider_item' => [
      'base hook' => 'paragraph',
    ],
  ];
}

/**
 * Implements hook_preprocess_paragraph().
 */
function your_module_preprocess_paragraph(&$variables) {
  $paragraph = $variables['paragraph'];
  
  if ($paragraph->bundle() == 'slider_with_resources') {
    // Add Slick library
    $variables['#attached']['library'][] = 'slick/slick';
    $variables['#attached']['library'][] = 'your_module/slider_resources';
    
    // Process slider items
    $slider_items = [];
    if ($paragraph->hasField('field_slider_items') && !$paragraph->get('field_slider_items')->isEmpty()) {
      foreach ($paragraph->get('field_slider_items')->referencedEntities() as $item) {
        $slider_items[] = $item;
      }
    }
    $variables['slider_items'] = $slider_items;
  }
  
  if ($paragraph->bundle() == 'slider_item') {
    // Process resources for show/hide functionality
    $resources = [];
    if ($paragraph->hasField('field_slide_resources') && !$paragraph->get('field_slide_resources')->isEmpty()) {
      foreach ($paragraph->get('field_slide_resources')->referencedEntities() as $resource) {
        $resources[] = [
          'title' => $resource->label(),
          'url' => $resource->toUrl()->toString(),
        ];
      }
    }
    $variables['resources'] = $resources;
  }
}

// 6. Library definition
// File: modules/custom/your_module/your_module.libraries.yml

/*
slider_resources:
  version: 1.x
  js:
    js/slider-resources.js: {}
  css:
    theme:
      css/slider-resources.css: {}
  dependencies:
    - core/jquery
    - core/drupal
    - slick/slick
*/

/* CSS File: css/slider-resources.css */

.slider-with-resources-wrapper {
  margin: 2rem 0;
}

.slider-container {
  position: relative;
}

.slider-item {
  padding: 2rem 0;
  min-height: 400px;
}

.slide-image img {
  width: 100%;
  height: auto;
  border-radius: 8px;
  box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}

.slide-content {
  padding: 1rem;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.slide-title {
  color: #333;
  margin-bottom: 1rem;
  font-weight: 600;
}

.slide-description {
  margin-bottom: 1.5rem;
  color: #666;
  line-height: 1.6;
}

.slide-resources h4 {
  color: #333;
  margin-bottom: 1rem;
  font-size: 1.1rem;
  font-weight: 600;
}

.resources-list {
  list-style: none;
  padding: 0;
  margin-bottom: 1rem;
}

.resource-item {
  margin-bottom: 0.5rem;
  transition: all 0.3s ease;
}

.resource-item.hidden-resource {
  display: none;
}

.resource-item.show {
  display: block;
  animation: fadeInUp 0.3s ease;
}

.resource-link {
  display: inline-block;
  color: #007bff;
  text-decoration: none;
  padding: 0.5rem 0;
  border-bottom: 1px solid transparent;
  transition: all 0.2s ease;
}

.resource-link:hover {
  color: #0056b3;
  text-decoration: none;
  border-bottom-color: #0056b3;
}

.show-more-resources {
  transition: all 0.2s ease;
}

.show-more-resources:hover {
  transform: translateY(-1px);
}

/* Slick slider customizations */
.slick-dots {
  bottom: -50px;
}

.slick-dots li button:before {
  color: #007bff;
  font-size: 12px;
}

.slick-dots li.slick-active button:before {
  color: #0056b3;
}

.slick-prev, .slick-next {
  z-index: 2;
}

.slick-prev:before, .slick-next:before {
  color: #007bff;
  font-size: 20px;
}

@keyframes fadeInUp {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* Responsive adjustments */
@media (max-width: 768px) {
  .slider-item {
    text-align: center;
  }
  
  .slide-content {
    margin-top: 1rem;
  }
  
  .slick-dots {
    bottom: -30px;
  }
}

// JavaScript File: js/slider-resources.js

(function ($, Drupal) {
  'use strict';

  Drupal.behaviors.sliderResources = {
    attach: function (context, settings) {
      // Initialize Slick slider
      $('.slider-container', context).each(function() {
        var $slider = $(this);
        
        // Initialize slick if not already initialized
        if (!$slider.hasClass('slick-initialized') && !$slider.data('slick-processed')) {
          $slider.slick();
          $slider.data('slick-processed', true);
        }
      });

      // Handle show more/less resources functionality
      $('.show-more-resources', context).each(function() {
        var $button = $(this);
        
        // Check if already processed to avoid duplicate event handlers
        if (!$button.data('show-more-processed')) {
          $button.data('show-more-processed', true);
          
          $button.on('click', function(e) {
            e.preventDefault();
            
            var $resourcesList = $button.siblings('.resources-list');
            var $hiddenResources = $resourcesList.find('.hidden-resource');
            var $showText = $button.find('.show-text');
            var $hideText = $button.find('.hide-text');
            
            if ($hiddenResources.first().hasClass('show')) {
              // Hide resources
              $hiddenResources.removeClass('show').fadeOut(300, function() {
                $(this).hide();
              });
              $showText.removeClass('d-none');
              $hideText.addClass('d-none');
              $button.blur();
            } else {
              // Show resources
              $hiddenResources.addClass('show').fadeIn(300);
              $showText.addClass('d-none');
              $hideText.removeClass('d-none');
              $button.blur();
            }
          });
        }
      });
    }
  };

})(jQuery, Drupal);

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Resources</title>
<style>
.hidden-resource {
display: none;
}
.hide-more-resources {
display: none;
cursor: pointer;
color: blue;
}
.show-more-resources {
cursor: pointer;
color: blue;
}
.card {
border: 1px solid #ccc;
padding: 16px;
margin-bottom: 16px;
width: fit-content;
}
</style>
</head>
<body>

<div class="card">
<div class="resource-list">
<div class="resource-item">
<div class="resource-item__content">
<h3 class="resource-item__title">This is title</h3>
<p class="resource-item__description"> This is description This is description This is description This is description This is description </p>
</div>
</div>
<div class="resource-item">
<div class="resource-item__content">
<h3 class="resource-item__title">This is title</h3>
<p class="resource-item__description"> This is description This is description This is description This is description This is description </p>
</div>
</div>
<div class="resource-item hidden-resource">
<div class="resource-item__content">
<h3 class="resource-item__title">This is title</h3>
<p class="resource-item__description"> This is description This is description This is description This is description This is description </p>
</div>
</div>
<div class="resource-item hidden-resource">
<div class="resource-item__content">
<h3 class="resource-item__title">This is title</h3>
<p class="resource-item__description"> This is description This is description This is description This is description This is description </p>
</div>
</div>
</div>
<div class="show-more-resources">2 more</div>
<div class="hide-more-resources">Show less</div>
</div>
<div class="card">
<div class="resource-list">
<div class="resource-item">
<div class="resource-item__content">
<h3 class="resource-item__title">This is title</h3>
<p class="resource-item__description"> This is description This is description This is description This is description This is description </p>
</div>
</div>
<div class="resource-item">
<div class="resource-item__content">
<h3 class="resource-item__title">This is title</h3>
<p class="resource-item__description"> This is description This is description This is description This is description This is description </p>
</div>
</div>
<div class="resource-item hidden-resource">
<div class="resource-item__content">
<h3 class="resource-item__title">This is title</h3>
<p class="resource-item__description"> This is description This is description This is description This is description This is description </p>
</div>
</div>
<div class="resource-item hidden-resource">
<div class="resource-item__content">
<h3 class="resource-item__title">This is title</h3>
<p class="resource-item__description"> This is description This is description This is description This is description This is description </p>
</div>
</div>
</div>
<div class="show-more-resources">2 more</div>
<div class="hide-more-resources">Show less</div>
</div>

<script>
document.addEventListener('DOMContentLoaded', function() {
const cards = document.querySelectorAll('.card');

cards.forEach(card => {
const showMoreBtn = card.querySelector('.show-more-resources');
const hideMoreBtn = card.querySelector('.hide-more-resources');
const hiddenResources = card.querySelectorAll('.hidden-resource');

showMoreBtn.addEventListener('click', () => {
hiddenResources.forEach(resource => {
resource.style.display = 'block';
});
showMoreBtn.style.display = 'none';
hideMoreBtn.style.display = 'block';
});

hideMoreBtn.addEventListener('click', () => {
hiddenResources.forEach(resource => {
resource.style.display = 'none';
});
showMoreBtn.style.display = 'block';
hideMoreBtn.style.display = 'none';
});
});
});
</script>

</body>
</html>

Comments

Popular posts from this blog

Opencart error: Notice: Trying to access array offset on value of type null in ..../vendor/scss.inc.php on line 1753

Collection of customer function and its description in Opencart