globular-mvc
Version:
Generic template to create web-application that made use of globular as backend and materialize as css (wrap in web-component's)
513 lines (424 loc) • 15 kB
JavaScript
import { SearchVideoCard, SearchAudioCard } from './Search';
export class Carousel extends HTMLElement {
constructor() {
super()
// Set the shadow dom.
this.attachShadow({ mode: 'open' });
// Innitialisation of the layout.
this.shadowRoot.innerHTML = `
<style>
/* page styles */
.ax-hidden {
visibility: hidden;
position: absolute;
}
/* carousel styles */
.carousel {
--carousel-height: 400px;
--carousel-width: 1000px;
--carousel-item-height: 150px;
--carousel-item-width: 150px;
width: 100%;
}
.carousel-container {
align-items: center;
display: flex;
min-height: var(--carousel-height);
margin: 0 auto;
max-width: var(--carousel-width);
position: relative;
background: radial-gradient(circle, rgb(170 170 170 / 0%) 0%, rgb(66 66 66 / 0%) 44%, var(--palette-background-default) 100%);
}
.carousel-item {
height: var(--carousel-item-height);
opacity: 0;
position: absolute;
transform: translateX(-50%);
transition: all 0.3s ease-in-out;
width: var(--carousel-item-width);
z-index: 0;
background-color: var(--palette-background-paper);
color: var(--palette-text-primary);
}
.carousel-item-1 {
left: 15%;
opacity: 0.4;
}
.carousel-item-2,
.carousel-item-4 {
height: calc(var(--carousel-item-height) * 1.5);
opacity: 1;
width: calc(var(--carousel-item-width) * 1.5);
z-index: 1;
}
.carousel-item-2 {
left: 30%;
}
.carousel-item-3 {
box-shadow: 0px 6px 14px -1px rgba(0,0,0,0.75);
-webkit-box-shadow: 0px 6px 14px -1px rgba(0,0,0,0.75);
-moz-box-shadow: 0px 6px 14px -1px rgba(0,0,0,0.75);
height: calc(var(--carousel-item-height) * 2);
opacity: 1;
left: 50%;
width: calc(var(--carousel-item-width) * 2);
z-index: 2;
}
.carousel-item-4 {
left: 70%;
}
.carousel-item-5 {
left: 85%;
opacity: 0.4;
}
.carousel-controls {
display: flex;
justify-content: center;
margin: 30px 0;
}
/* carousel button styles */
.carousel-control {
background-color: transparent;
border: 2px solid;
border-radius: 4px;
color: #aaa;
cursor: pointer;
height: 22px;
margin: 0 20px;
position: relative;
transform: scale(1.5);
transition: transform 0.5s ease-out;
width: 22px;
}
.carousel-control:hover {
transform: scale(1.3);
}
/* previous button */
.carousel-control-previous::after,
.carousel-control-previous::before {
box-sizing: border-box;
content: '';
display: block;
height: 8px;
position: absolute;
top: 5px
}
.carousel-control-previous::before {
background: currentColor;
border-radius: 2px;
right: 11px;
width: 2px;
}
.carousel-control-previous::after {
border-bottom: 4px solid transparent;
border-right: 5px solid;
border-top: 4px solid transparent;
right: 5px;
width: 0;
}
/* next button */
.carousel-control-next::after,
.carousel-control-next::before {
box-sizing: border-box;
content: "";
display: block;
height: 8px;
position: absolute;
top: 5px
}
.carousel-control-next::before {
background: currentColor;
border-radius: 2px;
left: 11px;
width: 2px;
}
.carousel-control-next::after {
border-bottom: 4px solid transparent;
border-left: 5px solid;
border-top: 4px solid transparent;
left: 5px;
width: 0;
}
/* play button */
.carousel-control-play::before {
border-bottom: 5px solid transparent;
border-left: 6px solid;
border-top: 5px solid transparent;
box-sizing: border-box;
content: "";
display: block;
height: 10px;
position: absolute;
left: 7px;
top: 4px;
width: 0;
}
/* pause button */
.carousel-control-play.playing::before {
border-bottom: 0;
border-left: 2px solid;
border-right: 2px solid;
border-top: 0;
box-sizing: border-box;
content: "";
display: block;
height: 6px;
position: absolute;
left: 6px;
top: 6px;
width: 6px;
}
/* add button */
.carousel-control-add::after,
.carousel-control-add::before {
background: currentColor;
border-radius: 5px;
box-sizing: border-box;
content: "";
display: block;
height: 2px;
position: absolute;
left: 4px;
top: 8px;
width: 10px;
}
.carousel-control-add::after {
height: 10px;
left: 8px;
top: 4px;
width: 2px;
}
.carousel-position {
font-size: 1.4rem;
font-weight: 600;
color: #aaa;
}
</style>
<div id="container" class="carousel">
<div class="carousel-container">
</div>
<div style="display:flex; align-items: center;">
<div class="carousel-controls" style="flex-grow: 1;">
</div>
<div class="carousel-position">
<span class="carousel-index"></span>/<span class="carousel-total"></span>
</div>
</div>
</div>
`
this.el = this.shadowRoot.querySelector("#container");
this.carouselIndex = this.shadowRoot.querySelector(".carousel-index")
this.carouselTotal = this.shadowRoot.querySelector(".carousel-total")
// Here I will not display the add option...
this.carouselOptions = ['previous', /*'add',*/ 'play', 'next'];
this.carouselData = [];
// The number of card visible.
this.carouselInView = [1, 2, 3, 4, 5];
this.carouselContainer = this.shadowRoot.querySelector(".carousel-container");
this.carouselPlayState;
}
connectedCallback() {
this.setupCarousel();
}
setEditable(editable) {
this.editable = editable;
let videoCards = this.shadowRoot.querySelectorAll("globular-search-video-card")
for (var i = 0; i < videoCards.length; i++) {
videoCards[i].setEditable(editable)
}
let audioCards = this.shadowRoot.querySelectorAll("globular-search-audio-card")
for (var i = 0; i < audioCards.length; i++) {
audioCards[i].setEditable(editable)
}
}
// Build carousel html
setupCarousel() {
// Create the container
const container = this.shadowRoot.querySelector(".carousel-container");
this.carouselContainer.innerHTML = ""
// Create the control div.
const controls = this.shadowRoot.querySelector(".carousel-controls");
controls.innerHTML = ""
// Take dataset array and append items to container
this.carouselData.forEach((data, index) => {
if (index < 5) {
const carouselItem = document.createElement('div');
container.append(carouselItem);
// Add item attributes
carouselItem.className = `carousel-item carousel-item-${index + 1}`;
let card = null
if (data.getDescription) {
card = new SearchVideoCard
card.setVideo(data)
} else if (data.getTitle) {
card = new SearchAudioCard
card.setAudio(data)
}
card.style.minWidth = "0px"
card.onclose = () => {
this.removeItem(index, data)
}
card.setEditable(this.editable)
carouselItem.appendChild(card)
carouselItem.setAttribute('loading', 'lazy');
// Used to keep track of carousel items, infinite items possible in carousel however min 5 items required
carouselItem.setAttribute('data-index', `${index + 1}`);
}
});
this.carouselOptions.forEach((option) => {
const btn = document.createElement('button');
const axSpan = document.createElement('span');
// Add accessibilty spans to button
axSpan.innerText = option;
axSpan.className = 'ax-hidden';
btn.append(axSpan);
// Add button attributes
btn.className = `carousel-control carousel-control-${option}`;
btn.setAttribute('data-name', option);
// Add carousel control options
controls.append(btn);
});
// After rendering carousel to our DOM, setup carousel controls' event listeners
this.setControls([...controls.children]);
// Set container property
this.carouselContainer = container;
}
setControls(controls) {
controls.forEach(control => {
control.onclick = (event) => {
event.preventDefault();
// Manage control actions, update our carousel data first then with a callback update our DOM
this.controlManager(control.dataset.name);
};
});
}
controlManager(control) {
if (control === 'previous') return this.previous();
if (control === 'next') return this.next();
if (control === 'add') return this.add();
if (control === 'play') return this.play();
return;
}
previous() {
// Update order of items in data array to be shown in carousel
this.carouselData.unshift(this.carouselData.pop());
// Push the first item to the end of the array so that the previous item is front and center
this.carouselInView.push(this.carouselInView.shift());
this.carouselIndex.innerHTML = this.carouselData[0].index + 1
// Update the css class for each carousel item in view
this.carouselInView.forEach((item, index) => {
this.carouselContainer.children[index].className = `carousel-item carousel-item-${item}`;
});
// Using the first 5 items in data array update content of carousel items in view
this.carouselData.slice(0, 5).forEach((data, index) => {
this.el.querySelector(`.carousel-item-${index + 1}`).innerHTML = ""
let card = null
if (data.getDescription) {
card = new SearchVideoCard
card.setVideo(data)
} else if (data.getTitle) {
card = new SearchAudioCard
card.setAudio(data)
}
card.style.minWidth = "0px"
card.onclose = () => {
this.removeItem(index, data)
}
card.setEditable(this.editable)
this.el.querySelector(`.carousel-item-${index + 1}`).appendChild(card)
});
}
next() {
// Update order of items in data array to be shown in carousel
this.carouselData.push(this.carouselData.shift());
// Take the last item and add it to the beginning of the array so that the next item is front and center
this.carouselInView.unshift(this.carouselInView.pop());
// Update the css class for each carousel item in view
this.carouselInView.forEach((item, index) => {
this.carouselContainer.children[index].className = `carousel-item carousel-item-${item}`;
});
this.carouselIndex.innerHTML = this.carouselData[0].index + 1
// Using the first 5 items in data array update content of carousel items in view
this.carouselData.slice(0, 5).forEach((data, index) => {
this.el.querySelector(`.carousel-item-${index + 1}`).innerHTML = ""
let card = null
if (data.getDescription) {
card = new SearchVideoCard
card.setVideo(data)
} else if (data.getTitle) {
card = new SearchAudioCard
card.setAudio(data)
}
card.style.minWidth = "0px"
card.onclose = () => {
this.removeItem(index, data)
}
card.setEditable(this.editable)
this.el.querySelector(`.carousel-item-${index + 1}`).appendChild(card)
});
}
// Set the carousel items.
setItems(items) {
items.forEach((item, index) => {
item.index = index;
})
this.carouselData = items
this.carouselTotal.innerHTML = items.length
this.carouselIndex.innerHTML = this.carouselData[0].index + 1
}
// Add a new item
add(newItem) {
const lastItem = this.carouselData.length;
newItem.index = lastIndex;
const lastIndex = this.carouselData.findIndex(item => item.id == lastItem);
// Then add it to the "last" item in our carouselData
this.carouselData.splice(lastIndex + 1, 0, newItem);
// set the total of items.
this.carouselTotal.innerHTML = this.carouselData.length
// Shift carousel to display new item
this.next();
}
// remove index from the carousel...
removeItem(index, data) {
this.carouselData = this.carouselData.filter(e => e.getId() !== data.getId())
this.carouselTotal.innerHTML = this.carouselData.length
this.next();
if (this.parentNode) {
if (this.parentNode.removeVideo) {
this.parentNode.removeVideo(data.file.path)
} else if (this.parentNode.removeAudio) {
this.parentNode.removeAudio(data.file.path)
}
}
// Not enought element to display carrousel
if (this.carouselData.length < this.carouselInView.length) {
// This will remove the carousel from the view...
if (this.parentNode) {
if (this.parentNode.setVideos) {
this.parentNode.setVideos(this.carouselData)
} else if (this.parentNode.setAudios) {
this.parentNode.setAudios(this.carouselData)
}
}
}
}
play() {
const playBtn = this.el.querySelector('.carousel-control-play');
const startPlaying = () => this.next();
if (playBtn.classList.contains('playing')) {
// Remove class to return to play button state/appearance
playBtn.classList.remove('playing');
// Remove setInterval
clearInterval(this.carouselPlayState);
this.carouselPlayState = null;
} else {
// Add class to change to pause button state/appearance
playBtn.classList.add('playing');
// First run initial next method
this.next();
// Use play state prop to store interval ID and run next method on a 1.5 second interval
this.carouselPlayState = setInterval(startPlaying, 1500);
};
}
}
customElements.define('globular-carousel', Carousel)