Readme

https://tmcw-poll.express.val.run

This val, along with a val called poll_results and a val called poll_options, lets you conduct a little poll using just Val Town! With the express API, this exposes a web interface in which people can click an option and vote. We make sure everyone only votes once by setting a cookie in their browser.

This uses htmx to make the voting experience slick, Tailwind to style the UI, and htm to generate HTML for the pages.

If you want to use this yourself, fork this val, the poll_results val, and the poll_options val, and customize the options list. You can delete the existing results as well to clear the data.

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
import { poll_results } from "https://esm.town/v/tmcw/poll_results";
import { poll_options } from "https://esm.town/v/tmcw/poll_options";
export let poll = async (req: express.Request, res: express.Response) => {
const { default: htm } = await import("npm:htm");
const { default: vhtml } = await import("npm:vhtml");
const cookies = await import("https://deno.land/std@0.193.0/http/cookie.ts");
const html = htm.bind(vhtml);
const head = html`<head>
<title>Poll</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/htmx.org@1.9.2"></script>
</head>`;
const ChartPie =
html`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-4">
<path d="M12 9a1 1 0 01-1-1V3c0-.553.45-1.008.997-.93a7.004 7.004 0 015.933 5.933c.078.547-.378.997-.93.997h-5z" />
<path d="M8.003 4.07C8.55 3.992 9 4.447 9 5v5a1 1 0 001 1h5c.552 0 1.008.45.93.997A7.001 7.001 0 012 11a7.002 7.002 0 016.003-6.93z" />
</svg>
`;
const formClass = "p-2 group min-h-screen animate-all bg-pink-50";
function parseCookies(cookie: string) {
const out: Record<string, string> = {};
const c = cookie.split(";");
for (const kv of c) {
const [cookieKey, ...cookieVal] = kv.split("=");
const key = cookieKey.trim();
out[key] = cookieVal.join("=");
}
return out;
}
function getResults(hasVoted: boolean) {
return html`<html>
${head}
<body>
<form hx-post="/" class=${formClass}>
<h1 class="text-sm font-bold uppercase text-pink-900 pb-2 flex items-center gap-x-2">
${ChartPie}
poll results
</h1>
<div class='space-y-2'>
${
poll_options.map((option, i) => {
const count = poll_results.filter((result) => {
return result.value === option;
}).length;
const percentage = (count / poll_results.length) *
100;
return html`<div class='p-2 rounded-md block text-sm
bg-pink-100 border border-pink-500
text-pink-700 overflow-hidden
relative flex justify-between'>
<div class='absolute top-0 left-0 bottom-0 bg-pink-200 z-0'
style=${`width: ${percentage}%`} />
<div class='z-1 relative'>${option}</div>
<div class='z-1 relative'>${count.toLocaleString("en-US")} / ${
(percentage).toFixed(0)
}%</div>
</div>`;
})
}</div>
<div class='text-center pt-2'>
${
hasVoted
? html`<div class='text-pink-300 cursor-not-allowed text-xs'>You've voted</div>`
: html`<a href='/' class='text-pink-500 underline hover:text-pink-700 text-xs'>Vote</a>`
}</div>
</form>
</body>
</html>`;
}
switch (req.method) {
case "POST": {
const value = req.body;
if (!("choice" in value)) {
return res.end("Bad input");
}
poll_results.push({
value: poll_options[value.choice],
time: (new Date()).toISOString(),
});
res.set("Set-Cookie", "voted=true; Path=/; HttpOnly");
res.end(getResults(true));
return;
}
default: {
const cookies = parseCookies(req.get("cookie"));
const hasVoted = "voted" in cookies;
if ("results" in req.query || hasVoted) {
return res.end(getResults(hasVoted));
}
else {
return res.end(html`<html>
${head}
<body>
<form hx-post="/"
class=${formClass}
👆 This is a val. Vals are TypeScript snippets of code, written in the browser and run on our servers. Create scheduled functions, email yourself, and persist small pieces of data — all from the browser.