forked from UWAppDev/uwappdev.github.io
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathreplacements.js
More file actions
222 lines (182 loc) · 6.7 KB
/
replacements.js
File metadata and controls
222 lines (182 loc) · 6.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
'use strict';
/**
* This script defines and fills the content of
* standard elements; e.g. the footer.
*/
const PAGES_DIRECTORY = "/pages";
const IMAGES_DIRECTORY = "/images";
const CONTENT_REPLACEMENTS =
{
'footer':
`
<div class = "img" style = "background-image: Url(${IMAGES_DIRECTORY}/joinUs.png);"></div>
<div class = "content">
<p><a href = "${PAGES_DIRECTORY}/join.html"><i>Join Us!</i></a>
Do you have a project you want to share?
A framework you want to learn? A team you want to create?
Something else? Join us! All University of Washington
students are welcome!
</p>
<p>
We have taught Flutter, SwiftUI, and even introductory Android
development! Want to learn or teach a different platform?
<a href = "https://discord.gg/ZyDM5Br">Let us know.</a>
</p>
</div>
`,
'header':
`
<a href = "${PAGES_DIRECTORY}/about.html">About</a>
<a href = "${PAGES_DIRECTORY}/support.html">Support Us</a>
<a href = "${PAGES_DIRECTORY}/join.html">Join Us</a>
`,
};
/**
* replaceSelectors replaces all selectors provided in CONTENT_REPLACEMENTS
* with their content, as defined in CONTENT_REPLACEMENTS. replaceSelectors
* is async, allowing calling code to push itself to the next animation frame
* for further DOM manipulations.
*/
async function replaceSelectors()
{
for (const selector in CONTENT_REPLACEMENTS)
{
const elements = document.querySelectorAll(selector);
for (const element of elements)
{
element.innerHTML = CONTENT_REPLACEMENTS[selector];
}
}
}
/**
* Find all <details> ... </details> elements
* and make their resize animatable.
*/
async function makeDropdownsAnimatable()
{
const DROPDOWN_ANIMATION_TIME_MS = 500;
const dropdowns = document.querySelectorAll("details");
let nextDropdownIdNumber = 0;
// Render an arrow onto a canvas. The arrow points
//right and can be rotated using CSS.
const renderArrow = (canvas) =>
{
const ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(ctx.canvas.width, ctx.canvas.height / 2);
ctx.lineTo(0, ctx.canvas.height);
ctx.lineTo(0, 0);
ctx.fill();
};
// Configure a single dropdown element.
// requires that each dropdown's first child is its title.
// Content MUST be wrapped in a div after the title.
const configureDropdown = async (elem) =>
{
if (!elem || !elem.children
|| elem.children.length !== 2) // Only animate if exactly two children.
{
return;
}
const titleHTML = elem.children[0].innerHTML; // Assumes elem.children[0] is the title.
const contentHTML = elem.children[1].innerHTML;
const parentElem = elem.parentElement;
// Add the container.
const dropdownContainer = parentElem.insertBefore(
document.createElement('div'), // New node
elem); // referenceNode (what newNode is inserted before).
// Add any additional class labels to the dropdown container.
for (const newClass of elem.classList)
{
if (newClass)
{
dropdownContainer.classList.add(newClass);
}
}
// Remove the element.
parentElem.removeChild(elem);
// Create portions of the new dropdown.
const titleContainer = document.createElement("div");
const contentContainer = document.createElement("div");
// Create portions of the title.
const arrow = document.createElement("canvas");
const title = document.createElement("div");
// Fill content.
title.innerHTML = titleHTML;
contentContainer.innerHTML = contentHTML;
// Styling
title.classList.add('content'); // title and contentContainer have different parent elements, so both can have class content...
arrow.classList.add('arrow');
titleContainer.classList.add('title');
contentContainer.classList.add('content'); // ...we can still distinguish them.
dropdownContainer.classList.add('dropdown');
// Completely decorative.
// Ref: https://a11yportal.com/advanced/design/images-svg-and-canvas.html
arrow.setAttribute("role", "presentation");
const contentContainerId = 'dropdown' + (nextDropdownIdNumber++);
contentContainer.setAttribute('id', contentContainerId);
// Resize the arrow (should be resized also by CSS).
arrow.width = 200;
arrow.height = 200;
renderArrow(arrow);
const updateAccessibilityState = () =>
{
const isVisible = dropdownContainer.classList.contains('expanded');
titleContainer.setAttribute('aria-expanded', isVisible);
contentContainer.setAttribute('aria-hidden', !isVisible);
};
const updateContentDisplay = () =>
{
const visible = dropdownContainer.classList.contains('expanded');
if (visible)
{
contentContainer.style.display = "block";
}
else
{
contentContainer.style.display = "none";
}
};
// Events.
const toggleState = () =>
{
if (dropdownContainer.classList.contains('expanded'))
{
dropdownContainer.classList.remove('expanded');
setTimeout(updateContentDisplay, DROPDOWN_ANIMATION_TIME_MS);
}
else
{
dropdownContainer.classList.add('expanded');
updateContentDisplay();
}
updateAccessibilityState();
};
titleContainer.addEventListener('click', () => { toggleState(); });
titleContainer.addEventListener('keydown', (e) => { if (e.keyCode == 13) { toggleState(); }}); // On enter-key press.
// Usability.
titleContainer.setAttribute('tabIndex', 0);
titleContainer.setAttribute('role', 'button');
titleContainer.setAttribute('aria-controls', contentContainerId);
updateAccessibilityState();
updateContentDisplay();
// Build element hierarchy
dropdownContainer.appendChild(titleContainer);
dropdownContainer.appendChild(contentContainer);
titleContainer.appendChild(arrow);
titleContainer.appendChild(title);
return true;
};
for (const elem of dropdowns)
{
await configureDropdown(elem);
}
}
// On page load...
async function main()
{
await replaceSelectors();
await makeDropdownsAnimatable();
}
main();