Skip to content

Instantly share code, notes, and snippets.

@jbrains
Last active April 2, 2017 21:06
Show Gist options
  • Save jbrains/4dbb425486fc4dcb14285d00c4d65016 to your computer and use it in GitHub Desktop.
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.
<!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>
@jbrains
Copy link
Author

jbrains commented Apr 2, 2017

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. :)

@hughrawlinson
Copy link

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.

@jbrains
Copy link
Author

jbrains commented Apr 2, 2017

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. :)

@jbrains
Copy link
Author

jbrains commented Apr 2, 2017

$ 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