Expand my Community achievements bar.

SOLVED

Card Carousel Component

Avatar

Level 2

Description

 

As a developer:

 

I want the carousel component to dynamically pull content from content fragments that are set in the components dialog properties o that the content can be updated in the fragments and automatically displayed in the carousel.

 

Acceptance Criteria:

The author sets the list of content fragments in the components dialog then the component fetches content from selected content fragments.

Content fragment details (e.g., title, description, images) are displayed correctly within each slide.

Changes in content fragments reflect in the carousel component without manual updates.

 

Can anyone provide any example or anything related to this will be really helpful and here what is the exact expections would help me.

 

Regards

Vinith M

1 Accepted Solution

Avatar

Correct answer by
Level 8

Hi @Vineeee 

 

On simple straight forwars way yhat I see is this:

  • Add a mtifield to your component
  • The item in the mutifield you be a simple Granite path selector field that will hold path to your CF.
  • In this was you can add as many dialog items you like
  • Each dialog item will represent a carousel item
  • Then have a Sling model that reads all this dialog items, extract the path to CF and reads the CF data
  • Have some HTL code in your component that uses the model to get the CFs data and assume to pass it on to some UI component that actuall renders the carousel

View solution in original post

13 Replies

Avatar

Correct answer by
Level 8

Hi @Vineeee 

 

On simple straight forwars way yhat I see is this:

  • Add a mtifield to your component
  • The item in the mutifield you be a simple Granite path selector field that will hold path to your CF.
  • In this was you can add as many dialog items you like
  • Each dialog item will represent a carousel item
  • Then have a Sling model that reads all this dialog items, extract the path to CF and reads the CF data
  • Have some HTL code in your component that uses the model to get the CFs data and assume to pass it on to some UI component that actuall renders the carousel

Avatar

Level 8

PS: Sorry for the typos. I responded from mobile, and it seams the responsive site does not have Edit option to my comment.

 

Avatar

Level 5

Hi @Vineeee 

 

Please find the below example you are looking for - 

 

1. Provide a CF path in dialog as below 

 
PRATHYUSHA_VP_0-1731564275083.png

 

 

GitHub core component examples :

 

https://github.com/adobe/aem-core-wcm-components/tree/main/content/src/content/jcr_root/apps/core/wc...

 

https://github.com/adobe/aem-core-wcm-components/blob/main/bundles/core/src/main/java/com/adobe/cq/w...

 

 

2. Read the CF path in sling model, retrieve the properties using Content Fragment API and implement getters and setters for  title, description and images 

 

Core component content fragment is implemented this way, you can check the implementation and override the component 

 

Hope this helps ! 

 

Thanks

Prathyusha

Avatar

Community Advisor

Hi @Vineeee 
You can use core carousel component and add items as content fragments, and browse the content fragment

https://experienceleague.adobe.com/en/docs/experience-manager-core-components/using/wcm-components/c... 

 



Arun Patidar

Avatar

Level 2

Hi @arunpatidar 

Can u please  explain with some more steps it would be  really helpful.Add items as Content Fragments should it be done programatically from Sling Model? or any other way.

 

Avatar

Level 2

Hi @arunpatidar,
Thank you for your suggestion! Could you please explain the steps in more detail? Specifically:

  • Should the items (slides) be added as Content Fragments programmatically using a Sling Model?
  • Or is there an out-of-the-box way, like updating the Core Carousel Component dialog to select Content Fragments?
    Your guidance would be really helpful.

Avatar

Community Advisor

Hi @Vinithm2 

Step1 - Allow content fragment component in the allowed component list from Carousel component 's policy

Step2 - Identify content fragment Items to create card and write CSS , step mentioned by @PRATHYUSHA_VP 

 

you don't need to override anything until you can manage styling of content fragment model using CSS.



Arun Patidar

Avatar

Level 2
 

Hi @arunpatidar  Following our earlier conversation, I successfully implemented the functionality to pick images from Content Fragments and generate responsive renditions using srcsetfor different devices. However, I want the image widths to be dynamically driven by the content policy instead of being hardcoded. Despite trying multiple approaches, I couldn’t achieve this.

 

Two Approaches Tried to Avoid Hardcoding Image Widths

  1. Direct Policy-Based Widths:

    • Fetched image widths from the content policy in the Dynamic Carousel Model.
    • Dynamically constructed the srcset using the image path from the Content Fragment and policy-defined widths.
    • Issue: This approach lacked Core Image model features, and on-the-fly rendition generation didn’t work reliably without Dynamic Media.
  2. Processing CF Images with Core Image Model:

    • Adapted images from Content Fragments to the Core Image model to leverage policy-based responsive renditions.
    • Used the Core Image model’s srcset and content policy configurations for image widths.
    • Issue: The Core Image model is tightly coupled with drag-and-drop authoring, making integration with CF-driven static images complex and less maintainable.

Both approaches were unsuitable for seamlessly achieving the requirement.

 

 

import com.adobe.cq.dam.cfm.ContentFragment;
import com.adobe.cq.dam.cfm.FragmentData;
import com.adobe.cq.wcm.core.components.models.Carousel;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.SlingObject;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class DynamicCardCarouselModel implements Carousel {

    @SlingObject
    private ResourceResolver resourceResolver;

    @ValueMapValue
    private String[] contentFragments;

    private List<ContentFragmentData> fragmentDataList;

    @PostConstruct
    protected void init() {
        fragmentDataList = new ArrayList<>();

        if (contentFragments == null || contentFragments.length == 0) {
            return;
        }

        for (String fragmentPath : contentFragments) {
            if (StringUtils.isNotBlank(fragmentPath)) {
                processFragmentPath(fragmentPath);
            }
        }
    }

    /**
     * Processes a single Content Fragment path and extracts its data if valid.
     *
     * @param fragmentPath The Content Fragment path to process.
     */
    private void processFragmentPath(String fragmentPath) {
        Resource fragmentResource = resourceResolver.getResource(fragmentPath);
        if (fragmentResource == null) {
            return;
        }

        ContentFragment contentFragment = fragmentResource.adaptTo(ContentFragment.class);
        if (contentFragment == null) {
            return;
        }

        ContentFragmentData data = extractContentFragmentData(contentFragment);
        if (data != null) {
            fragmentDataList.add(data);
        }
    }

    /**
     * Extracts relevant data from a Content Fragment.
     *
     * @param contentFragment The content fragment to extract data from.
     * @return A ContentFragmentData object containing the fragment's title, description, image path, and link URL.
     */
    private ContentFragmentData extractContentFragmentData(ContentFragment contentFragment) {
        String title = getFragmentStringProperty(contentFragment, "cardTitle");
        String description = getFragmentStringProperty(contentFragment, "cardDescription");
        String imagePath = getFragmentStringProperty(contentFragment, "cardImage");
        String linkURL = getFragmentStringProperty(contentFragment, "cardLinkURL");
        return new ContentFragmentData(title, description, imagePath, linkURL);
    }

    /**
     * Retrieves a string property value from a Content Fragment.
     *
     * @param contentFragment The content fragment.
     * @param propertyName    The name of the property to retrieve.
     * @return The string value of the property, or an empty string if not found.
     */

    private String getFragmentStringProperty(ContentFragment contentFragment, String propertyName) {
        if (contentFragment.hasElement(propertyName)) {
            FragmentData fragmentData = contentFragment.getElement(propertyName).getValue();
            return fragmentData.getValue(String.class);
        }
        return "";
    }


    /**
     * Exposes the list of content fragment data for rendering.
     *
     * @return List of ContentFragmentData objects.
     */
    public List<ContentFragmentData> getFragmentDataList() {
        return fragmentDataList != null ? fragmentDataList : Collections.emptyList();
    }

    /**
     * Inner class representing the data of a content fragment.
     */
    public static class ContentFragmentData {
        private final String title;
        private final String description;
        private final String imagePath;
        private final String linkURL;

        public ContentFragmentData(String title, String description, String imagePath, String linkURL) {
            this.title = title;
            this.description = description;
            this.imagePath = imagePath;
            this.linkURL = linkURL;
        }

        public String getTitle() {
            return title;
        }

        public String getDescription() {
            return description;
        }

        public String getImagePath() {
            return imagePath;
        }

        public String getLinkURL() {
            return linkURL;
        }
    }
} 

 

 

 

<sly data-sly-use.dynamicCarousel="com.v2.core.models.DynamicCardCarouselModel">
    <sly data-sly-use.inPageNavModel="com.v2.core.models.InPageNavigationModel">

        <div class="cmp-card-carousel" data-cmp-is="card-carousel" id="${carousel.id}">

            <div class="cmp-card-carousel__wrapper" data-sly-attribute.id="${inPageNavModel.currentResourceInPageNavID}">

                <div class="cmp-card-carousel__controls">
                    <button class="swiper-button swiper-button--prev">
                        <span class="visually-hidden">Previous</span>
                    </button>
                    <button class="swiper-button swiper-button--next">
                        <span class="visually-hidden">Next</span>
                    </button>
                </div>

                <div class="cmp-card-carousel__swiper swiper">
                    <sly data-sly-test="${dynamicCarousel.fragmentDataList.size > 0}">

                        <div class="swiper-wrapper" data-sly-list.fragment="${dynamicCarousel.fragmentDataList}">

                            <div class="swiper-slide" role="tabpanel" data-cmp-hook-carousel="item" aria-roledescription="slide">

                                <div class="cmp-card--carousel-item-inner${fragment.linkURL ? ' clickable' : ''}">
                                    <div class="cmp-card__wrapper">
                                        <div class="cmp-card__header">
                                            <img src="${fragment.imagePath}" 
                                                 srcset="${fragment.imagePath}?width=320 320w, 
                                                         ${fragment.imagePath}?width=520 520w, 
                                                         ${fragment.imagePath}?width=960 960w" 
                                                 sizes="(max-width: 480px) 320px, 
                                                        (min-width: 481px) and (max-width: 768px) 640px, 
                                                        (min-width: 769px) 960px" 
                                                 alt="${fragment.title}">
                                        </div>
                                        <div class="cmp-card__contents">
                                            <h5 class="cmp-card__title">
                                                <a href="${fragment.linkURL}" target="_blank">${fragment.title}</a>
                                            </h5>
                                            <div class="cmp-card__description">
                                                <p>${fragment.description @ context='html'}</p>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>

                        </div>

                    </sly>
                </div>

                <div class="cmp-card-carousel__scrollbar">
                    <div class="swiper-scrollbar"></div>
                </div>
            </div>

        </div>

    </sly>
</sly>

 

 

Avatar

Administrator

@Vineeee  Did you find the suggestions helpful? Please let us know if you require more information. Otherwise, please mark the answer as correct for posterity. If you've discovered a solution yourself, we would appreciate it if you could share it with the community. Thank you!



Kautuk Sahni

Avatar

Level 2

Yes It was really helpful but i do not have the option to mark as correct answer please help me or if possible mark as the correct answer