Last active
April 2, 2017 21:06
-
-
Save jbrains/4dbb425486fc4dcb14285d00c4d65016 to your computer and use it in GitHub Desktop.
Finding functional/reusable bits in a small amount of Javascript. Especially trying to push details up the call stack.
This file contains hidden or 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 lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>JS Drum Kit</title> | |
<link rel="stylesheet" href="style.css"> | |
</head> | |
<body> | |
<div class="keys"> | |
<div data-key="65" class="key"> | |
<kbd>A</kbd> | |
<span class="sound">clap</span> | |
</div> | |
<div data-key="83" class="key"> | |
<kbd>S</kbd> | |
<span class="sound">hihat</span> | |
</div> | |
<div data-key="68" class="key"> | |
<kbd>D</kbd> | |
<span class="sound">kick</span> | |
</div> | |
<div data-key="70" class="key"> | |
<kbd>F</kbd> | |
<span class="sound">openhat</span> | |
</div> | |
<div data-key="71" class="key"> | |
<kbd>G</kbd> | |
<span class="sound">boom</span> | |
</div> | |
<div data-key="72" class="key"> | |
<kbd>H</kbd> | |
<span class="sound">ride</span> | |
</div> | |
<div data-key="74" class="key"> | |
<kbd>J</kbd> | |
<span class="sound">snare</span> | |
</div> | |
<div data-key="75" class="key"> | |
<kbd>K</kbd> | |
<span class="sound">tom</span> | |
</div> | |
<div data-key="76" class="key"> | |
<kbd>L</kbd> | |
<span class="sound">tink</span> | |
</div> | |
</div> | |
<section id="drum-sounds"> | |
<audio data-key="65" src="sounds/clap.wav"></audio> | |
<audio data-key="83" src="sounds/hihat.wav"></audio> | |
<audio data-key="68" src="sounds/kick.wav"></audio> | |
<audio data-key="70" src="sounds/openhat.wav"></audio> | |
<audio data-key="71" src="sounds/boom.wav"></audio> | |
<audio data-key="72" src="sounds/ride.wav"></audio> | |
<audio data-key="74" src="sounds/snare.wav"></audio> | |
<audio data-key="75" src="sounds/tom.wav"></audio> | |
<audio data-key="76" src="sounds/tink.wav"></audio> | |
</section> | |
<script> | |
// Generic Javascript | |
function applySafely(f, x) { | |
return (x ? f.call(null, x) : null); | |
} | |
// Generic DOM manipulation | |
// CONTRACT: turnElementOn and turnElementOff are functions of a DOM element | |
// ASSUMES that domElement generates a transition event when it is turned on | |
function pulseUiElement(domElement, turnElementOn, turnElementOff) { | |
// ASSUMES that the DOM event's target is the receiver of addEventListener() | |
domElement.addEventListener('transitionend', event => turnElementOff.call(null, event.target)); | |
turnElementOn.call(null, domElement); | |
} | |
// Generic HTML element behavior | |
// "rewind" means "prepare the audio element to play again" | |
// CONTRACT: audioElement is an <audio> element, so it can be rewound and played | |
function rewindThenPlay(audioElement) { | |
audioElement.currentTime = 0; | |
audioElement.play(); | |
} | |
// Specific to this document | |
// CONTRACT: I can change the CSS class on domElement | |
function markUiElementPlaying(domElement) { | |
domElement.classList.add("playing"); | |
} | |
function markUiElementNotPlaying(domElement) { | |
domElement.classList.remove("playing"); | |
} | |
function pulseUiElementPlaying(domElement) { | |
pulseUiElement(domElement, markUiElementPlaying, markUiElementNotPlaying); | |
} | |
function playSoundAndPulseUi(domElement, soundKeyCode) { | |
const audioElement = domElement.querySelector(`audio[data-key="${soundKeyCode}"]`); | |
applySafely(rewindThenPlay, audioElement); | |
const keyUiElement = domElement.querySelector(`.key[data-key="${soundKeyCode}"]`); | |
applySafely(pulseUiElementPlaying, keyUiElement); | |
} | |
window.addEventListener('keydown', event => playSoundAndPulseUi(document, event.keyCode)); | |
</script> | |
</body> | |
</html> |
Looks great! Minor changes I'd make out of habit:
- put everything in a closure to stop myself accidentally putting things in global scope
- declare functions like "function name()" because it gives you better stack traces.
- investigate using the web audio API because it lets you mix audio better which is pretty important but often overlooked. You do have to deal with imperatively manipulating graph state though, which isn't so fun in a functional context.
I really like the transitionend event, I see "settimeout" used SO much.
Thanks, @hughrawlinson! I'm just throwing everything into global scope for now, because I'm (re)learning more basic language constructs and gradually (re)discovering the DOM API. I don't think I'll get to multiple modules for a while. When it becomes "too easy", then I'll add that constraint.
I didn't know about the difference in declaring functions. Thanks.
As for the transition events, I worry a little about spreading around the assumption of which things fire transition events, but I'll live with it for the moment. I'll let myself stumble upon that as I go. :)
$ gloga
* 43da0e7 (HEAD -> jbrains) We now declare functions with a syntax that @hughrawlinson told me provides better stack traces.
[...]
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I received the HTML; I wrote the Javascript. I am open to critiques and suggestions.
I intentionally split the Javascript into tiny functions, even though this might look like overkill for "such a simple situation". I'm doing at least two things: (1) Filling in the gaps of my knowledge of Javascript; (2) Trying to push my functional thinking to its limits. Accordingly, please don't just inline everything and tell me that that's better. :)