-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding initial version of carousel demo files
- Loading branch information
0 parents
commit a516178
Showing
7 changed files
with
1,049 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
# canvas-carousel-demo | ||
|
||
## Overview | ||
|
||
This repository includes a collection of HTML, image, and JSON files that together form an announcement carousel | ||
that can be installed or embedded on the Canvas home page (i.e. Dashboard). | ||
The carousel makes use of Bootstrap (version 5), Bootstrap Icons, and native HTML and JavaScript functionality. | ||
|
||
This carousel has been checked for accessibility and can be navigated with the keyboard and will announced the alt text if provided. | ||
|
||
## Repository setup | ||
|
||
This demo site can be forked on Github and either be left public or made private. Or you can just copy the files and host it where you like on S3 or any web host. At Michigan we just run the private repository as a Github Page the similar to the demo and make all our changes right in Github via the GUI. However we have an AWS CloudFront distribution setup as Github Pages has bandwidth limits. We typically have about 10 million requests and 500GB of traffic in a typical month and the costs total $10/month. | ||
|
||
If you change the name of this repository you'll have to change the value of pathStem in the html file to something else. This may be useful to be configurable in the future. | ||
|
||
`const pathStem = '/canvas-carousel-demo/';` | ||
|
||
## Canvas installation | ||
|
||
To enable the carousel, a Canvas admin must include the JavaScript below as part of a larger file uploaded in the Canvas custom theme editor. | ||
Replace `{your domain}` with a domain that is publicly serving the `canvas_carousel_player.html` file | ||
and the `content` and `data` directories under the `/canvas-carousel-demo/` path. | ||
U-M ITS Teaching & Learning uses GitHub Pages to accomplish this, serving from [`tl-its-umich-edu.github.io/canvas-carousel-demo/](https://tl-its-umich-edu.github.io/canvas-carousel-demo/). | ||
|
||
``` | ||
$(document).ready(function() { | ||
// Add Carousel | ||
$("#dashboard").before(` | ||
<div style="margin-bottom: 25px;"> | ||
<iframe src='https://tl-its-umich-edu.github.io/canvas-carousel-demo/canvas_carousel_player.html' width="650" height="200" role="complementary" style="border: none;"> | ||
</iframe> | ||
</div> | ||
`); | ||
}); | ||
``` | ||
|
||
Once added, the carousel will launch and begin cycling slides when the Canvas home page is loaded. | ||
|
||
## Adding slide content | ||
|
||
To add one or more new slides, perform the following steps in order: | ||
|
||
1) Add one or more new image files to the [`/content/` directory](/content/) in this repository. | ||
2) For each new file, add a new object to the existing array under the `"slides"` key in the JSON file | ||
at [`data/slide-data.json`](/data/slide-data.json). | ||
Each object must contain the following keys and appropriate string values: | ||
- `imageFileName`: the name of an image file located in [`/content/`](/content/) in this repository | ||
- `linkURL`: a URL to send the user to when they select the slide | ||
- `altText`: alternative text for the slide image for use by assistive technologies | ||
|
||
A template file can be found at [`data/slide-data-template.json`](/data/slide-data-template.json). | ||
|
||
3) Before saving or committing changes to the JSON file, check that all key-value pairs are present and the JSON is valid. | ||
|
||
To remove slides from the carousel, remove the corresponding objects from the array. | ||
Slides will be presented in the order they are defined in the array. | ||
While the carousel does not programmatically limit the number of slides, | ||
a maximum of four slides is recommended to lessen the cognitive load on users. | ||
|
||
## Settings | ||
|
||
The carousel also supports a single setting currently that controls whether the carousel starts on a random slide. | ||
To change this setting, modify the value for the `"startRandom"` key in the object under the `"settings"` key in the | ||
[`data/slide-data.json`](/data/slide-data.json) file. A `true` value will make the starting slide random, | ||
`false` will make the starting slide be the first slide in the array of objects defined under `"slides"` key. | ||
|
||
## Local development | ||
|
||
Since this application is a collection of basic static files, developers can test changes using a simple server | ||
on `localhost`. There are a number of ways to accomplish; essentially, you serve the parent directory of the | ||
`canvas-carousel-demo` directory. | ||
|
||
For example, you can use the following steps in order to use a simple Python Web server. | ||
|
||
1) Navigate to the project directory. | ||
``` | ||
git clone https://github.com/tl-its-umich-edu/canvas-carousel-demo.git | ||
cd canvas-carousel-demo | ||
``` | ||
|
||
2) Run the server using `http`, targeting the parent directory. | ||
``` | ||
python3 -m http.server --directory . | ||
``` | ||
|
||
3) In your browser of choice, navigate to | ||
[`http://localhost:8000/canvas-carousel-demo/canvas_carousel_player.html`](http://localhost:8000/canvas-carousel-demo/canvas_carousel_player.html). | ||
|
||
## Resources | ||
|
||
- [Canvas Theme Editor](https://community.canvaslms.com/t5/Admin-Guide/How-do-I-create-a-theme-for-an-account-using-the-Theme-Editor/ta-p/242) | ||
- [Bootstrap Carousel](https://getbootstrap.com/docs/5.0/components/carousel/) | ||
- [Boostrap Icons](https://icons.getbootstrap.com/) | ||
- [Mozilla Developer Network - Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,251 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>carousel</title> | ||
|
||
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous"> | ||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script> | ||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css"> | ||
|
||
<style type="text/css" media="screen"> | ||
|
||
/* Custom colors */ | ||
:root { | ||
--mi-blue: #00274C; | ||
--mi-blue-light: #0070D9; | ||
} | ||
|
||
/* Layout */ | ||
#canvas-carousel { | ||
width: 650px; | ||
height: 198.2px; | ||
cursor: pointer; | ||
} | ||
|
||
.carousel-inner > .carousel-item > a { | ||
margin-left: 50px; | ||
} | ||
|
||
.carousel-inner { | ||
border: 1px solid #dddddd; | ||
margin-block-end: 0px; | ||
} | ||
|
||
ul, menu, dir { | ||
-webkit-padding-start: 0px; | ||
} | ||
|
||
ul, menu, dir { | ||
-moz-padding-start: 0px; | ||
} | ||
|
||
/* Controls */ | ||
|
||
.btn-outline-primary { | ||
--bs-btn-color: var(--mi-blue); | ||
--bs-btn-border-color: var(--mi-blue); | ||
--bs-btn-hover-color: white; | ||
--bs-btn-hover-bg: var(--mi-blue-light) !important; | ||
--bs-btn-hover-border-color: var(--mi-blue); | ||
--bs-btn-active-color: white; | ||
--bs-btn-active-bg: var(--mi-blue); | ||
--bs-btn-active-border-color: var(--mi-blue); | ||
} | ||
|
||
/* Previous/Next */ | ||
.carousel-control-prev, .carousel-control-next { | ||
width: 50px; | ||
height: 170px; | ||
z-index: 2; | ||
opacity: 1.0 !important; | ||
} | ||
|
||
.carousel-control-prev .bi-chevron-left, .carousel-control-next .bi-chevron-right { | ||
color: var(--mi-blue); | ||
font-size: 32px; | ||
} | ||
|
||
.carousel-control-prev .bi-chevron-left:hover, .carousel-control-next .bi-chevron-right:hover { | ||
color: var(--mi-blue-light); | ||
} | ||
|
||
/* visible focus rings */ | ||
.carousel-control-prev:focus-visible, .carousel-control-next:focus-visible, .carousel-item a:focus-visible { | ||
outline: var(--bs-blue) auto 1px !important; | ||
} | ||
|
||
/* Play/Pause */ | ||
#play-pause { | ||
position: absolute; | ||
bottom: 35px; | ||
right: 9px; | ||
z-index: 999; | ||
} | ||
|
||
/* Indicators */ | ||
.carousel-indicators { | ||
margin-bottom: 0 !important; | ||
} | ||
|
||
.carousel-indicators button { | ||
height: 15px !important; | ||
width: 15px !important; | ||
border-radius: 100% !important; | ||
opacity: 1 !important; | ||
text-indent: 0 !important; | ||
color: var(--mi-blue); | ||
border: 1px solid var(--mi-blue) !important; | ||
margin-left: 5px !important; | ||
margin-right: 5px !important; | ||
margin-bottom: 5px; | ||
margin-top: 5px; | ||
} | ||
|
||
.carousel-indicators :is(button.active, button.active:hover, button.active:focus-visible) { | ||
background-color: var(--mi-blue) !important; | ||
color: white !important; | ||
} | ||
|
||
.carousel-indicators :is(button:hover, button:focus-visible) { | ||
background-color: var(--mi-blue-light) !important; | ||
color: white !important; | ||
} | ||
|
||
@media (prefers-reduced-motion) { | ||
body { | ||
display: none | ||
} | ||
} | ||
|
||
</style> | ||
</head> | ||
|
||
<body class="d-none d-sm-block"> | ||
<div | ||
id="canvas-carousel" | ||
class="carousel slide carousel-fade" | ||
role="complementary" | ||
aria-label="Announcement Carousel" | ||
> | ||
<!-- Previous --> | ||
<button class="carousel-control-prev" data-bs-target="#canvas-carousel" data-bs-slide="prev" title="Previous Slide" aria-label="Previous Slide"> | ||
<div class="bi bi-chevron-left"></div> | ||
</button> | ||
<!-- Wrapper for slides --> | ||
<ul id="slide-list" class="carousel-inner" aria-live="polite"> | ||
</ul> | ||
<!-- Next --> | ||
<button class="carousel-control-next" data-bs-target="#canvas-carousel" data-bs-slide="next" title="Next Slide" aria-label="Next Slide"> | ||
<div class="bi bi-chevron-right"></div> | ||
</button> | ||
<!-- Wrapper for indicators --> | ||
<div id="slide-indicators" class="carousel-indicators"></div> | ||
<!-- Play/Pause --> | ||
<div id="play-pause"> | ||
<button id="pause" data-bs-toggle="button" class="btn btn-sm btn-outline-primary" aria-label="Pause carousel"> | ||
<div id="play-pause-icon" class="bi bi-pause-fill"></div> | ||
</button> | ||
</div> | ||
</div> | ||
</body> | ||
<script> | ||
const pathStem = '/canvas-carousel-demo/'; | ||
const imagePathStem = pathStem + 'content/'; | ||
const dataPathStem = pathStem + 'data/'; | ||
|
||
/* | ||
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random | ||
*/ | ||
function getRandomIndex (length) { | ||
if (length < 1) return 0; | ||
const max = Math.floor(length - 1); | ||
return Math.floor(Math.random() * (max + 1)); | ||
} | ||
|
||
function SlideEl (slide, active) { | ||
const imgEl = document.createElement('img'); | ||
imgEl.setAttribute('src', imagePathStem + slide.imageFileName); // safe sink | ||
imgEl.setAttribute('alt', slide.altText); // safe sink | ||
|
||
const linkEl = document.createElement('a'); | ||
linkEl.setAttribute('href', slide.linkURL); // safe sink | ||
linkEl.setAttribute('target', '_blank'); | ||
linkEl.append(imgEl); | ||
|
||
const captionEl = document.createElement('p'); | ||
captionEl.classList.add('carousel-caption', 'visually-hidden'); | ||
captionEl.setAttribute('id', 'carousel-caption1'); | ||
captionEl.innerText = 'Select the link to learn more.'; | ||
|
||
const listItemEl = document.createElement('li'); | ||
listItemEl.classList.add('carousel-item'); | ||
if (active === true) listItemEl.classList.add('active'); | ||
listItemEl.append(linkEl, captionEl); | ||
return listItemEl; | ||
} | ||
|
||
function IndicatorEl (index, active) { | ||
const slideNumStr = String(index + 1); | ||
const label = 'Slide ' + slideNumStr | ||
const buttonEl = document.createElement('button'); | ||
buttonEl.setAttribute('data-bs-target', '#canvas-carousel'); | ||
buttonEl.setAttribute('data-bs-slide-to', String(index)); | ||
buttonEl.setAttribute('aria-label', label); | ||
buttonEl.setAttribute('title', label); | ||
buttonEl.classList.add('btn', 'btn-outline-primary'); | ||
if (active === true) { | ||
buttonEl.classList.add('active'); | ||
buttonEl.setAttribute('aria-current', 'true'); | ||
} | ||
return buttonEl; | ||
} | ||
|
||
async function addElements () { | ||
const resp = await fetch(dataPathStem + 'slide-data.json'); | ||
const { slides, settings } = await resp.json(); | ||
const startingIndex = settings.startRandom === true ? getRandomIndex(slides.length) : 0; | ||
const slideListEl = document.getElementById('slide-list'); | ||
const indicatorContainer = document.getElementById('slide-indicators'); | ||
|
||
slides.forEach((s, i) => { | ||
slideListEl.append(SlideEl(s, i === startingIndex)); | ||
indicatorContainer.append(IndicatorEl(i, i === startingIndex)); | ||
}); | ||
} | ||
|
||
async function main () { | ||
await addElements(); | ||
|
||
const canvasCarouselEl = document.getElementById('canvas-carousel'); | ||
const pauseEl = document.getElementById('pause'); | ||
const playPauseIconEl = document.getElementById('play-pause-icon'); | ||
|
||
const canvasCarousel = new bootstrap.Carousel(canvasCarouselEl, { | ||
interval: 11000, pause: false | ||
}); | ||
canvasCarousel.cycle(); | ||
|
||
let paused = false; | ||
pauseEl.addEventListener( | ||
'click', | ||
() => { | ||
if (paused) { | ||
canvasCarousel.cycle(); | ||
playPauseIconEl.classList.remove('bi-play-fill'); | ||
playPauseIconEl.classList.add('bi-pause-fill'); | ||
paused = false; | ||
} else { | ||
canvasCarousel.pause(); | ||
playPauseIconEl.classList.remove('bi-pause-fill'); | ||
playPauseIconEl.classList.add('bi-play-fill'); | ||
paused = true; | ||
} | ||
} | ||
); | ||
} | ||
|
||
main().catch((r) => console.error(String(r))); | ||
</script> | ||
</html> |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"slides": [ | ||
{ | ||
"imageFileName": "", | ||
"altText": "", | ||
"linkURL": "" | ||
} | ||
], | ||
"settings": { | ||
"startRandom": true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{ | ||
"slides": [ | ||
{ | ||
"imageFileName": "sample_image_1.png", | ||
"altText": "Sample Image 1", | ||
"linkURL": "https://github.com/tl-its-umich-edu/" | ||
}, | ||
{ | ||
"imageFileName": "sample_image_2.png", | ||
"altText": "Sample Image 2", | ||
"linkURL": "https://github.com/tl-its-umich-edu/" | ||
} | ||
], | ||
"settings": { | ||
"startRandom": true | ||
} | ||
} |