mirror of
https://github.com/JeremyLikness/vanillajs-deck
synced 2025-12-18 04:03:58 +00:00
Add controls and SPA routing
This commit is contained in:
21
js/app.js
21
js/app.js
@@ -1,23 +1,10 @@
|
||||
import { getJson } from "./jsonLoader.js"
|
||||
import { loadSlides } from "./slideLoader.js"
|
||||
import { Navigator } from "./navigator.js"
|
||||
|
||||
const state = {
|
||||
manifest: {}
|
||||
};
|
||||
import { registerDeck } from "./navigator.js"
|
||||
import { registerControls } from "./controls.js"
|
||||
|
||||
const app = async () => {
|
||||
|
||||
state.deck = document.getElementById("main");
|
||||
|
||||
// load the manifest
|
||||
state.manifest = await getJson("slides/manifest.json");
|
||||
|
||||
// load the slides
|
||||
state.slides = await loadSlides(state.manifest.start);
|
||||
|
||||
// initialize the navigation
|
||||
state.navigator = new Navigator(state.slides, state.deck);
|
||||
registerDeck();
|
||||
registerControls();
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", app);
|
||||
|
||||
54
js/controls.js
vendored
Normal file
54
js/controls.js
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
class Controls extends HTMLElement {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._controlRef = null;
|
||||
this._deck = null;
|
||||
}
|
||||
|
||||
async connectedCallback() {
|
||||
const response = await fetch("/templates/controls.html");
|
||||
const template = await response.text();
|
||||
this.innerHTML = "";
|
||||
const host = document.createElement("div");
|
||||
host.innerHTML = template;
|
||||
this.appendChild(host);
|
||||
this._controlRef = {
|
||||
first: document.getElementById("ctrlFirst"),
|
||||
prev: document.getElementById("ctrlPrevious"),
|
||||
next: document.getElementById("ctrlNext"),
|
||||
last: document.getElementById("ctrlLast"),
|
||||
pos: document.getElementById("position")
|
||||
};
|
||||
this._controlRef.first.addEventListener("click", () => this._deck.jumpTo(0));
|
||||
this._controlRef.prev.addEventListener("click", () => this._deck.previous());
|
||||
this._controlRef.next.addEventListener("click", () => this._deck.next());
|
||||
this._controlRef.last.addEventListener("click", () => this._deck.jumpTo(this._deck.totalSlides - 1));
|
||||
this.refreshState();
|
||||
}
|
||||
|
||||
static get observedAttributes() {
|
||||
return ["deck"];
|
||||
}
|
||||
|
||||
async attributeChangedCallback(attrName, oldVal, newVal) {
|
||||
if (attrName === "deck") {
|
||||
if (oldVal !== newVal) {
|
||||
this._deck = document.getElementById(newVal);
|
||||
this._deck.addEventListener("slideschanged", () => this.refreshState());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
refreshState() {
|
||||
const next = this._deck.hasNext;
|
||||
const prev = this._deck.hasPrevious;
|
||||
this._controlRef.first.disabled = !prev;
|
||||
this._controlRef.prev.disabled = !prev;
|
||||
this._controlRef.next.disabled = !next;
|
||||
this._controlRef.last.disabled = !next;
|
||||
this._controlRef.pos.innerText = `${this._deck.currentIndex + 1} / ${this._deck.totalSlides}`;
|
||||
}
|
||||
}
|
||||
|
||||
export const registerControls = () => customElements.define('slide-controls', Controls);
|
||||
@@ -1,9 +1,43 @@
|
||||
export class Navigator {
|
||||
import { loadSlides } from "./slideLoader.js"
|
||||
import { Router } from "./router.js"
|
||||
|
||||
constructor(slides, deck) {
|
||||
this._deck = deck;
|
||||
this._slides = slides;
|
||||
this.jumpTo(0);
|
||||
class Navigator extends HTMLElement {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._router = new Router();
|
||||
this._route = this._router.getRoute();
|
||||
this.slidesChangedEvent = new CustomEvent("slideschanged", {
|
||||
bubbles: true,
|
||||
cancelable: false
|
||||
});
|
||||
this._router.eventSource.addEventListener("routechanged", () => {
|
||||
if (this._route !== this._router.getRoute()) {
|
||||
this._route = this._router.getRoute();
|
||||
if (this._route) {
|
||||
var slide = parseInt(this._route) - 1;
|
||||
this.jumpTo(slide);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static get observedAttributes() {
|
||||
return ["start"];
|
||||
}
|
||||
|
||||
async attributeChangedCallback(attrName, oldVal, newVal) {
|
||||
if (attrName === "start") {
|
||||
if (oldVal !== newVal) {
|
||||
this._slides = await loadSlides(newVal);
|
||||
this._route = this._router.getRoute();
|
||||
var slide = 0;
|
||||
if (this._route) {
|
||||
slide = parseInt(this._route) - 1;
|
||||
}
|
||||
this.jumpTo(slide);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get currentIndex() {
|
||||
@@ -11,11 +45,11 @@ export class Navigator {
|
||||
}
|
||||
|
||||
get currentSlide() {
|
||||
return this._slides[this._currentIndex];
|
||||
return this._slides ? this._slides[this._currentIndex] : null;
|
||||
}
|
||||
|
||||
get totalSlides() {
|
||||
return this._slides.length;
|
||||
return this._slides ? this._slides.length : 0;
|
||||
}
|
||||
|
||||
get hasPrevious() {
|
||||
@@ -29,8 +63,11 @@ export class Navigator {
|
||||
jumpTo(slideIdx) {
|
||||
if (slideIdx >= 0 && slideIdx < this.totalSlides) {
|
||||
this._currentIndex = slideIdx;
|
||||
this._deck.innerHTML = '';
|
||||
this._deck.appendChild(this.currentSlide.html);
|
||||
this.innerHTML = '';
|
||||
this.appendChild(this.currentSlide.html);
|
||||
this._router.setRoute(slideIdx+1);
|
||||
this._route = this._router.getRoute();
|
||||
this.dispatchEvent(this.slidesChangedEvent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,4 +82,6 @@ export class Navigator {
|
||||
this.jumpTo(this.currentIndex - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const registerDeck = () => customElements.define('slide-deck', Navigator);
|
||||
30
js/router.js
Normal file
30
js/router.js
Normal file
@@ -0,0 +1,30 @@
|
||||
export class Router {
|
||||
|
||||
constructor() {
|
||||
this._eventSource = document.createElement("div");
|
||||
this._routeChanged = new CustomEvent("routechanged", {
|
||||
bubbles: true,
|
||||
cancelable: false
|
||||
});
|
||||
this._route = null;
|
||||
window.addEventListener("popstate", () => {
|
||||
if (this.getRoute() !== this._route) {
|
||||
this._route = this.getRoute();
|
||||
this._eventSource.dispatchEvent(this._routeChanged);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get eventSource() {
|
||||
return this._eventSource;
|
||||
}
|
||||
|
||||
setRoute(route) {
|
||||
window.location.hash = route;
|
||||
this._route = route;
|
||||
}
|
||||
|
||||
getRoute() {
|
||||
return window.location.hash.substr(1).replace(/\//ig, "/");
|
||||
}
|
||||
}
|
||||
@@ -11,10 +11,10 @@ export async function loadSlides(start) {
|
||||
const slides = [];
|
||||
const cycle = {};
|
||||
while (next) {
|
||||
const nextSlide = await loadSlide(next);
|
||||
if (!cycle[nextSlide.title]) {
|
||||
if (!cycle[next]) {
|
||||
cycle[next] = true;
|
||||
const nextSlide = await loadSlide(next);
|
||||
slides.push(nextSlide);
|
||||
cycle[nextSlide.title] = nextSlide;
|
||||
next = nextSlide.nextSlide;
|
||||
}
|
||||
else {
|
||||
|
||||
Reference in New Issue
Block a user