Multiple Slider
Description
It is a develop wich let you do one or various sliders with the strapi manager and with diferent types like:
-
Images with urls.
-
Videos to open in a modal.
-
Pdfs to see in a new tab and even download it.
Strapi structure
The multiple slider itself will be a repeatable component(named static-card) and will have the following fields:
-
slider_title: This text will be the slider title. It will be a string.
-
slider_type: It will be a selector that let you to choose the type of slider within a closed list of options. It will be a enumeration of texts
['images', 'videos', 'pdfs']. -
slider_card: It will be a repeatable component wich will reference to the cards within the slider. It will has the following fields:
-
url: It will be the url wich the card will redirect to. It will only be filled when the slider_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 slider_type is
'videos'or'pdf'. -
card_type: It will be a enumeration of texts
['images', 'videos', 'pdfs']. In this case with the multiple slider it will be the same as the field slider_type. This field will be truly used in the Multipurpose slider.
-
Code example
In this case we are gonna do the multiple sliders in the GU page of Oncology web, we will assume that we already have create the structure of strapi and filled the necessary data in the GU page 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();singleTypesApiServices.js
import { httpClient } from "@/services/useFetchApi";
/** * Fetches all gu page data. * * @async * @function * @returns {Object} The gu page data. * @throws {Error} Throws an error if there's an issue fetching the data. */export const getGU = async () => { const params = [ { name: "populate", value: "slider_partners.partners.white_logo" }, { name: "populate", value: "slider_banners.image_mobile" }, { name: "populate", value: "slider_banners.image_pc" }, { name: "populate", value: "static_slider.static_card.image" }, { name: "populate", value: "static_slider.repeatable_cards.image" }, { name: "populate", value: "multiple_sliders.slider_card.image" }, { name: "populate", value: "multiple_sliders.slider_card.multimedia_content", }, ]; const { response } = await httpClient.get("gu", params); return response;};We import the function that get the data into the file we are working with
GU/index.vue
import { getGU } from "@/services/singleTypesApiServices";You can learn more about Services here
We have to import the component of the multiple slider too
GU/index.vue
const SliderPartnerCard = defineAsyncComponent(() => import("@/components/sliders/SliderPartnerCard.vue"));We can destructure the object
GU/index.vue
const gu = await getGU();const { highlighted_text: highlightedText, slider_partners_title: sliderPartnersTitle, slider_banners: sliderBanners, slider_partners: sliderPartners, static_slider: staticSlider, multiple_sliders: multipleSliders,} = gu;And now we can print the component SliderPartnerCard with the necessary data in the html
GU/index.vue
<section v-for="(slider, index) in multipleSliders" :key="index"> <SliderPartnerCard :title="slider.slider_title" :repeatable_cards="slider.slider_card" :slider_type="slider.slider_type" ></SliderPartnerCard> </section>Now we will analize a bit the component SliderPartnerCard.vue
We create a object SLIDER_TYPES with some strings inside to not use strings directy in the conditions and evade the magic strings
SliderPartnerCard.vue
const SLIDER_TYPES = { images: "images", videos: "videos", pdfs: "pdfs",};We have three props that come from the father where we had called our component.
-
title: It will be an string. It will be the text of our card
-
repeatable_cards: It will be an array of objects with the data of every card
-
slider_type: It will be an array of strings with the following possible data
['images', 'videos', 'pdfs']
Going for the template, first of all we will draw the title using the title prop.
<h2 class="container-pictures__highlighted">{{ 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.
SliderPartnerCard.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’).
SliderPartnerCard.vue
<article class="container"> <ul class="static-slider"> <MainSlider class="training__slider"> <div v-for="(card, index) in repeatable_cards" :key="index" class="static-card-container" > // Here you continue drawing the card </div> </MainSlider> </ul></article>All the cards into every slider have a little functionality to do something depending on the slider_type of the card when you click on it.
-
If slider_type is
'images'it will open the url in a new tab -
If slider_type is
'videos'it will open a video modal -
If slider_type is
'pdfs'it will open the pdf in a new tab
SliderPartnerCard.vue
<li class="card-partner" @click=" slider_type === SLIDER_TYPES.videos ? [ (modal = !modal), (modalSrc = card.multimedia_content.url), playVideo(), ] : slider_type === SLIDER_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 slider_type
-
If slider_type is
'videos'it will draw a image of a play icon -
If slider_type is
'pdfs'it will draw a image of a arrow icon referencing a downloadable content
<div class="card-partner__logo-front"> <div class="card-partner__img"> <img :src="`${card.image.url}`" :alt="`${card.title}`" :title="`${card.title}`" class="card-partner__img" /> </div> <Top v-if="slider_type === SLIDER_TYPES.pdfs" :height="'6em'" class="block-front-downloadarrow" /> <Play v-if="slider_type === SLIDER_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-partner__title">{{ card.title }}</p>And finally we create the modal wich will be open when we click on a card wich his slider_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 slider_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();};