Skip to content

Multipurpose Slider

Description

It is a develop wich let you do one slider with the strapi manager and with diferent types of cards inside the slider like:

  • Images with urls.

  • Videos to open in a modal.

  • Pdfs to see in a new tab and even download it.

  • Expert card type wich will draw the expert card and have a redirection to his expert detail page.

  • Product card type wich will draw the product card and have a redirection to his product detail page.

  • Series card type wich will draw the serie card and have a redirection to his series detail page.

Strapi structure

The Multipurpose slider itself will have the following fields:

  • slider_multipurpose_title: This text will be the slider title. It will be a string.

  • A dynamic zone where you can insert components like:

    • expertCard component(to do the expert card type)

    • productCard component(to do the product card type)

    • seriesCard component(to do the series card type)

    • static-card component(to do the pdfs card type , videos card type and images with urls card type). This component in turn has the following fields:

      • url: It will be the url wich the card will redirect to. It will only be filled when the card_type is 'images'.

      • image: It will be the image draw inside the card. It will always be filled.

      • title: It will be the title behind the image. It will always be filled.

      • multimedia_content: It will be a mp4 or pdf file. In the case of an mp4 file when you click in the card it will open a modal wich will reproduce the video and in the case of a pdf file when you click in the card it will open the pdf in a new tab. It will only be filled when the card_type is 'videos' or 'pdf'.

      • card_type: It will be a enumeration of texts ['images', 'videos', 'pdfs'].

Code example

In this case we are gonna do the Multipurpose sliders in the detail page of the partner Merck in the Oncology web, we will assume that we already have create the structure of strapi and filled the necessary data in the partner of strapi.

First of all we will get the data of the page we are working with from the strapi database via async functions and fetch.

useFetchApi.js

class HttpClient {
constructor() {
this.STATUS = {
error: "error",
success: "success",
idle: "idle",
pending: "pending",
};
}
async get(resource, params = [], err = true) {
try {
if (!resource) throw new Error("Resource is not provided");
const formatParams = ref("");
params.forEach((param) => {
formatParams.value += `${param.name}=${param.value}&`;
});
if (formatParams.value === "") formatParams.value = "populate=*";
const {
data: {
value: { data: response },
},
status,
error,
pending,
} = await useFetch(
`${this.getUrl()}api/${resource}?${formatParams.value}`,
{
onResponse({
response: {
_data: { data: response },
},
}) {
if (response.length === 0 && err)
clearError({ redirect: "/error" });
},
}
);
if (status === this.STATUS.error && error.value)
throw new Error(error.value.message);
return { response, status, error, pending };
} catch (error) {
console.error(error);
throw error;
}
}
}
export const httpClient = new HttpClient();

partnersApiServices.js

import { httpClient } from "@/services/useFetchApi";
export const getSpecificPartner = async (slug) => {
const params = [
{ name: "filters[slug][$eq]", value: slug },
{ name: "populate", value: "header_image_webp" },
{ name: "populate", value: "header_image_jpg" },
{ name: "populate", value: "white_logo" },
{ name: "populate", value: "products" },
{ name: "populate", value: "products.partners.white_logo" },
{ name: "populate", value: "products.product_image_webp" },
{ name: "populate", value: "products.product_image_jpg" },
{ name: "populate", value: "experts" },
{ name: "populate", value: "experts.expert_imagewebp" },
{ name: "populate", value: "experts.expert_imagejpg" },
{ name: "populate", value: "multiple_sliders.slider_card.image" },
{
name: "populate",
value: "multiple_sliders.slider_card.multimedia_content",
},
{ name: "populate", value: "slider_multipurpose.image" },
{ name: "populate", value: "slider_multipurpose.multimedia_content" },
{ name: "populate", value: "slider_multipurpose.expert" },
{ name: "populate", value: "slider_multipurpose.expert.expert_imagewebp" },
{ name: "populate", value: "slider_multipurpose.expert.expert_imagejpg" },
{ name: "populate", value: "slider_multipurpose.product" },
{
name: "populate",
value: "slider_multipurpose.product.partners.white_logo",
},
{
name: "populate",
value: "slider_multipurpose.product.product_image_webp",
},
{
name: "populate",
value: "slider_multipurpose.product.product_image_jpg",
},
];
const { response } = await httpClient.get("partners", params);
return response;
};

We import the function that get the data into the file we are working with

partners/[title].vue

import { getSpecificPartner } from "@/services/partnersApiServices";

You can learn more about Services here

We use the imported function ‘getSpecificPartner’ to get the data of the partner where we are in and save it in a constant

partners/[title].vue

const [partner] = await getSpecificPartner(useRoute().params.title);

We have to import the component of the multipurpose slider too

partners/[title].vue

const SliderMultiPurpose = defineAsyncComponent(() =>
import("@/components/sliders/SliderMultipurpose.vue")
);

And now we can print the component SliderMultiPurpose with the necessary data in the html

partners/[title].vue

<div class="partner-detail__multipurpose-slider">
<SliderMultiPurpose :item="partner"></SliderMultiPurpose>
</div>

Now we will analize a bit the component SliderMultiPurpose.vue

We create some objects STATIC_COMPONENT_TYPES and COMPONENT_TYPES with some strings inside to not use strings directy in the conditions and evade the magic strings

SliderMultiPurpose.vue

const STATIC_COMPONENT_TYPES = {
images: "images",
videos: "videos",
pdfs: "pdfs",
};
const COMPONENT_TYPES = {
static: "cards.static-card",
expert: "cards.expert-card",
product: "cards.product-card",
};

We have one prop that come from the father where we had called our component.

  • item: It will be an object. It will have all the data of our partner

SliderMultiPurpose.vue

const props = defineProps({
item: {
type: Object,
required: true,
},
});

Going for the template, first of all we will draw the title getting it from the ‘item’ object.

SliderMultiPurpose.vue

<h2 class="slider-multipurpose__h2">{{ item.slider_multipurpose_title }}</h2>

We will use other component called MainSlider.vue that will help us to draw the side arrows of the slider and all our stuff inside via slots, more info about slots

MainSlider.vue

<script setup>
const props = defineProps({
height: { type: String, default: 'auto' },
showArrows: { type: String, default: 'always' },
})
const whiteColor = useWhiteColor()
</script>
<template>
<v-slide-group :height="height" tag="section" :show-arrows="showArrows">
<template #prev>
<SideArrow :iconColor="whiteColor" :style="'transform: rotateZ(180deg)'" />
</template>
<template #next>
<SideArrow :iconColor="whiteColor" />
</template>
<slot></slot>
</v-slide-group>
</template>

Do not forget to define the component before use it.

SliderMultiPurpose.vue

const MainSlider = defineAsyncComponent(() =>
import("@/components/common/MainSlider.vue")
);

The ‘article’ element will do as a container of the list (‘ul’) and the combination of the MainSlider(that will have the side arrows) and the cards we are drawing inside(with the ‘v-for’ and the prop of ‘repeatable_cards’) will be the slider inside the list(‘ul’).

SliderMultiPurpose.vue

<article class="slider-multipurpose__container">
<ul class="card-list">
<MainSlider>
<div
v-for="(card, index) in item.slider_multipurpose"
:key="index"
class="card-container"
>
// Here you continue drawing the card
</div>
</MainSlider>
</ul>
</article>

All the cards into the slider will have a little functionality to draw something depending on the COMPONENT_TYPES object:

  • if COMPONENT_TYPES is 'static'

    In the case of 'static' we have a little functionality to do something depending on the card_type of the card when you click on it.

    • If card_type is 'images' it will open the url in a new tab

    • If card_type is 'videos' it will open a video modal

    • If card_type is 'pdfs' it will open the pdf in a new tab

    SliderMultiPurpose.vue

    <div v-if="card.__component === COMPONENT_TYPES.static">
    <li
    class="card"
    @click="
    card.card_type === STATIC_COMPONENT_TYPES.videos
    ? [(modal = !modal), (modalSrc = card.multimedia_content.url)]
    : card.card_type === STATIC_COMPONENT_TYPES.images
    ? navigateTo(card.url, { external: true, open: { target: '_blank' } })
    : navigateTo(card.multimedia_content.url, {
    external: true,
    open: { target: '_blank' },
    })
    "
    >

    We have other functionality to draw the image of the card and put a another image on top of it depending of the card_type

    • If card_type is 'videos' it will draw a image of a play icon

    • If card_type is 'pdfs' it will draw a image of a arrow icon referencing a downloadable content

    <div class="card__logo-front">
    <div class="card__container-img">
    <img
    :src="`${card.image.url}`"
    :alt="`${card.title}`"
    :title="`${card.title}`"
    class="card__img"
    />
    </div>
    <Top
    v-if="card.card_type === STATIC_COMPONENT_TYPES.pdfs"
    :height="'6em'"
    class="block-front-downloadarrow"
    />
    <Play
    v-if="card.card_type === STATIC_COMPONENT_TYPES.videos"
    :height="'6em'"
    class="block-front-play"
    />
    </div>

    Next we will draw the title of the card wich goes below the previous image

    <p class="card__title">{{ card.title }}</p>
    </li>
    </div>
  • if COMPONENT_TYPES is 'expert'

You will import the component

const ExpertCard = defineAsyncComponent(() =>
import("@/components/expert/ExpertCard.vue")
);

And use it in the template

<div v-else-if="card.__component === COMPONENT_TYPES.expert">
<ExpertCard
:name="card.expert.expert_title"
:imgWebp="card.expert.expert_imagewebp?.url"
:imgJpg="card.expert.expert_imagejpg?.url"
:hasLink="true"
:slug="card?.expert.slug"
:isSelectItem="selectItem === index"
@click="selectExpert(0)"
/>
</div>
  • if COMPONENT_TYPES is 'products'

You will import the component

const CardTraining = defineAsyncComponent(() =>
import("@/components/training/CardTraining.vue")
);

And use it in the template

<div v-else-if="card.__component === COMPONENT_TYPES.product">
<CardTraining :item="card.product" />
</div>

And finally we create the modal wich will be open when we click on a card wich his card_type is 'videos'

<div class="block-modal" :class="modal ? 'abrirModal' : ''" :key="index">
<div class="block-modal__container">
<span class="block-modal__close" @click="[(modal = !modal), pausarVideo()]" />
<video
id="video-modal"
class="block-modal__video"
preload="metadata"
muted
controls
alt="Video kit digital"
:src="`${modalSrc}`"
autoplay
></video>
</div>
</div>

Remember that this modal will open when you click on a card if card_type is 'videos'.

Oh! do not forget to do a little functionality to stop the video when the user close the modal.

const playVideo = () => {
document.getElementById("video-modal").play();
};
const pausarVideo = () => {
document.getElementById("video-modal").pause();
};