Created
September 10, 2016 18:21
-
-
Save benchristel/61e71f3aa69deedafa7f3438f7a0d366 to your computer and use it in GitHub Desktop.
An example of how to adapt the Elm architecture to vanilla JS
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
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Working Title</title> | |
<style type="text/css"> | |
body { | |
margin: 0; | |
padding: 10px; | |
background-color: black; | |
color: goldenrod; | |
} | |
#terminal > p { | |
margin: 0; | |
padding: 0; | |
font-family: Menlo, Monaco, monospace; | |
font-size: 16px; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="terminal"> | |
<p id="0"></p> | |
<p id="1"></p> | |
<p id="2"></p> | |
<p id="3"></p> | |
<p id="4"></p> | |
<p id="5"></p> | |
<p id="6"></p> | |
<p id="7"></p> | |
<p id="8"></p> | |
<p id="9"></p> | |
<p id="10"></p> | |
<p id="11"></p> | |
<p id="12"></p> | |
<p id="13"></p> | |
<p id="14"></p> | |
<p id="15"></p> | |
<p id="16"></p> | |
<p id="17"></p> | |
<p id="18"></p> | |
<p id="19"></p> | |
<p id="20"></p> | |
<p id="21"></p> | |
<p id="22"></p> | |
<p id="23"></p> | |
<p id="24"></p> | |
<p id="25"></p> | |
<p id="26"></p> | |
</div> | |
<div id="state" style="white-space:pre;font-family:monospace;font-size:12px"> | |
</div> | |
<script type="text/javascript"> | |
"use strict"; | |
// --- graphix ----------------------------------------------------------------- | |
var terminal = document.getElementById('terminal') | |
var SCREEN_WIDTH = 80 | |
function truncateToScreenWidth(text) { | |
return text.slice(0, SCREEN_WIDTH) | |
} | |
function printScreen(lines) { | |
var i, line, truncatedLine, node, nodes | |
nodes = terminal.getElementsByTagName('p') | |
for (i = 0; i < nodes.length; i++) { | |
node = nodes[i] | |
line = lines[i] | |
if (typeof line !== 'undefined') { | |
truncatedLine = truncateToScreenWidth(line) | |
if (truncatedLine !== node.innerText) { | |
node.innerText = truncatedLine | |
} | |
} | |
} | |
} | |
// --- kernel ------------------------------------------------------------------ | |
// initial state | |
var state = { | |
render: splashScreen, | |
tick: tickOnSplashScreen, | |
ticksElapsed: 0, | |
} | |
function doEvent(handler, arg) { | |
state = handler(state, arg) | |
document.getElementById('state').innerText = debugState(state) | |
printScreen(state.render(state)) | |
} | |
function debugState(state) { | |
var output = '' | |
forEachOwnPropertyOf(state, (key, val) => { | |
output += key + ': (' + (typeof val) + ') ' + val + '\n' | |
}) | |
return output | |
} | |
doEvent(identity) | |
window.setInterval(function() { | |
if (typeof state.tick === 'function') { | |
doEvent(state.tick) | |
} | |
}, 250) | |
window.addEventListener('keypress', function(event) { | |
if (typeof state.keyPress === 'function') { | |
var char = String.fromCharCode(event.charCode) | |
doEvent(state.keyPress, char) | |
} | |
}) | |
// --- events ------------------------------------------------------------------ | |
function prompt() { | |
return { | |
render: promptScreen, | |
tick: blinkCursor, | |
keyPress: typeChar, | |
typed: '', | |
cursor: '_' | |
} | |
} | |
function blinkCursor(state) { | |
var newState = clone(state) | |
var countdown = | |
state.ticksUntilNextCursorBlink === undefined ? 0 | |
: state.ticksUntilNextCursorBlink | |
return merge(state, { | |
ticksUntilNextCursorBlink: | |
countdown === 0 | |
? 2 | |
: countdown - 1, | |
cursor: | |
countdown === 0 | |
? ( | |
state.cursor === '_' | |
? '' | |
: '_' | |
) | |
: state.cursor | |
}) | |
} | |
function typeChar(state, char) { | |
return merge(state, { | |
typed: state.typed + char | |
}) | |
} | |
function tickOnSplashScreen(state) { | |
if (state.ticksElapsed === 3) { | |
return prompt() | |
} else { | |
return merge(state, { | |
ticksElapsed: state.ticksElapsed + 1 | |
}) | |
} | |
} | |
// --- renderers --------------------------------------------------------------- | |
function splashScreen(state) { | |
return ['initializing...'] | |
} | |
function promptScreen(state) { | |
return ['> ' + state.typed + state.cursor] | |
} | |
// --- utility functions ------------------------------------------------------- | |
function identity(x) { | |
return x | |
} | |
function forEachOwnPropertyOf(obj, fn) { | |
for (var prop in obj) { | |
if (Object.prototype.hasOwnProperty.call(obj, prop)) { | |
fn(prop, obj[prop]) | |
} | |
} | |
} | |
function clone(obj) { | |
var copy = {} | |
forEachOwnPropertyOf(obj, (key, val) => { | |
copy[key] = val | |
}) | |
return copy | |
} | |
function merge(obj1, obj2) { | |
var merged = {} | |
forEachOwnPropertyOf(obj1, (key, val) => { | |
merged[key] = val | |
}) | |
forEachOwnPropertyOf(obj2, (key, val) => { | |
merged[key] = val | |
}) | |
return merged | |
} | |
/* Coerces the given thing to a number. | |
* If it's NaN, returns 0 instead. | |
*/ | |
function number(thing) { | |
var n = +thing | |
// n !== n iff it's NaN | |
return (n !== n) ? 0 : n | |
} | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment