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
Post a Comment