Matthew Gatland

Learning JavaScript without JQuery

August 13, 2018

I’m updating the Girl Code activities to no longer use jQuery. jQuery is less trendy and popular every year, and we need to teach our students stuff that will help them look good when they talk to experienced developers.

When we started, some simple tasks were just too complex without jQuery – in particular, making a GET request went from this:

$.get("/report", processReport);

to this:

var xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.open('GET', '/report');
xhr.onload = function() {
  var jsonResponse = xhr.response;
    processReport(jsonResponse);
};
xhr.send();

What a mess! For a student who’s trying to remember what ‘GET’ and ‘POST’ mean, the jQuery example is so much better.

But the web has matured, and in most browsers a GET request can now look like this:

fetch("/report")
  .then(response => response.json())
  .then(processReport);

We can work with that. It’s still not as good as the jQuery way – it doesn’t use the key word ‘GET’, and it introduces promises and JSON, concepts that we don’t want to teach at this point. But it’s short and inoffensive and it’ll be fine.

(Note that I’m not a JS expert, and there just the best examples I’ve found so far. Feel free to email me if you have something that’s better! By better I mean: it requires understanding fewer concepts.)

So anyway, the most essential thing you’ll ever want to do in JavaScript is create HTML and add it into a page. With vanilla JS, I haven’t found a way to do this that I like. I’ve found ways that are not too bad. Here are the alternatives I’ve come up with so far:

The step by step approach:

function displayMessage1(message) {
  let postElement = document.createElement("div");
  postElement.classList.add("post");

  let messageElement = document.createElement("div");
  messageElement.classList.add("message");
  messageElement.innerHTML = message;

  postElement.appendChild(messageElement);

  let messageList = document.querySelector(".message-list");
  messageList.appendChild(postElement);
}

I personally like this, because it forces students to think of HTML as a tree instead of text. But it’s a bit unfair to make them construct HTML in such an abstract way when they’ve only just learned how to write it the normal way.

Using a DOMParser:

function displayMessage2(message) {
  let postElement = new DOMParser().parseFromString('<div class="post"><div class="message">' + message + '</div></div>', 'text/html').body;

  let messageList = document.querySelector(".message-list");
  messageList.appendChild(postElement);
}

This is pretty good. It’s a shame you have to construct a new DOMParser, specify “text/html”, and add “.body” to get the actual element. It would be perfect without those three oddities.

function displayMessage3(message) {
  let container = document.createElement("div");
  container.innerHTML = '<div class="post"><div class="message">' + message + '</div></div>';
  let messageList = document.querySelector(".message-list");
  messageList.appendChild(container.firstChild);
}

Not sure if this is better or worse. I think it might be easier to explain what’s weird about it: we create a temporary throwaway div element to build inside, then use its contents. Our students already have to learn what innerHTML does so it’s not an additional load.

function displayMessage4(message) {
  let postElement = document.createRange().createContextualFragment('<div class="post"><div class="message">' + message + '</div></div>');
  let messageList = document.querySelector(".message-list");
  messageList.appendChild(postElement);
}

My favourite so far. It avoids any “.firstChild” weirdness and returns the element we asked for. It unfortunately introduces a bunch of nonsense words (range? contextual fragment?!?) but it’s all on one line, so we can handwave it and say “it makes some HTML”.

Any of these will be better with template literals (template strings), so we can have line breaks in the HTML template:

function displayMessage5(message) {
  let postElement = document.createRange().createContextualFragment(
    `
    <div class="post">
        <div class="message">${message}</div>
    </div>
    `);
  let messageList = document.querySelector(".message-list");
  messageList.appendChild(postElement);
}

Like everything, this is a tradeoff. Template literals are very different from normal strings, and teaching beginners both too closely together will add cognitive load. Should we use normal strings instead, broken into multiple lines?

    '<div class="post">' +
    '    <div class="message">' + message + '</div>' +
    '</div>'

They’ll make more mistakes with this version, but… good mistakes. You’ll never regret becoming a string expert.

I think my fav is version 5, but multiline strings instead of template literals.

Then later in the course, when we learn about using functions to avoid duplicate code, we could hide document.createRange().createContextualFragment inside a nicely named utility function.

Update

Alexey points out that you don’t need to create an HTML element if you append plain text HTML to the parent node’s inner HTML. That would come out like this:

function displayMessage6(message) {
  let postHtml = 
    `
    <div class="post">
        <div class="message">${message}</div>
    </div>
    `;
  let messageList = document.querySelector(".message-list");
  messageList.innerHTML += postHtml;
}

This is better. I didn’t think of this because I was looking up way to “create an HTML element”, which we don’t explicitly do here. It has a few limitations, but it’s simple and probably the best option for our course.

No TV

January 04, 2018

I’ve given up television… again.

Many years ago, I stopped watching broadcast TV. TV, I thought, is a waste of time. You sit there and watch random shows selected by someone else. Clever people like me download the shows we want to watch over the internet, and only watch stuff that’s really good and relevant to us. We don’t waste our time.

15 years later, the world was very different. The internet got faster, and TV shows become more common - so there were more shows I wanted to watch, and it was easier to download them. Netflix arrived and destroyed the final barriers. Now I could watch shows I liked continuously, without even taking a break to find new download links.

Watching TV become too easy. Any time I sat at my computer, I’d be tempted to open Netflix - especially if I was feeling tired or stressed. And because TV doesn’t make me less tired or less stressed, the desire to watch Netflix is never satiated. Each episode leaves me in the same state of mind I started in, ready to watch another episode.

The only way out that I could see was a total ban. No TV, unless I’m with someone else. (I don’t want to ostracise myself.)

So that’s what I did.

I originally aimed to do this for 100 days, but it was so good I’ve kept it going for much longer. I have had occasional cheat days - you might want to schedule these in officially if you don’t like cheating. Of course, it’s much better to use the loophole of convincing a friend to watch an important show with you.

The no-TV rule hasn’t changed my life that much. In particular, it hasn’t made me more productive or self-motivated. However, it has helped me spend my time on a much more diverse range of timewasting activities. Instead of watching TV, I might read a book, play a video game, or even spend a quiet moment with my thoughts.

I highly recommend giving up TV again. If you, like me, gave up broadcast television a long time ago, it’s worth thinking about how your relationship with TV has changed since then. Maybe it’s time to take another step back.

How to make YouTube (or other video websites) louder

November 24, 2017

Sometimes I find a YouTube video of a conference talk that’s just too quiet. Even when it’s set to maximum volume, with Windows set to maximum volume, it’s still too quiet.

The YouTube volume slider set to maximum

The Windows volume slider set to maximum

You can easily fix this with JavaScript!

First, open your web browser’s JavaScript console. In Chrome, click on the menu button, then ‘more tools’, then ‘Developer Tools’ to open the developer tools.

Opening the developer tools as described above

In the Developer Tools, click on the Console tab.

Open the console

Click in the empty white space after the > symbol. Now you can type or paste code into the console.

Click on the console

Copy this code and paste it into the console.

var videoElement = document.querySelector("video")
var audioCtx = new AudioContext()
var source = audioCtx.createMediaElementSource(videoElement)
var gainNode = audioCtx.createGain()
gainNode.gain.value = 2 // double the volume
source.connect(gainNode)
gainNode.connect(audioCtx.destination)

It will look like this once you paste it in:

The code, as it will appear in the console.

Press enter to send the code to your browser. The video should immediately get louder.

If you want to make it even louder, copy this line, paste it in and press enter. You can change the number to a higher value to make it even louder.

gainNode.gain.value = 3

The code, as it will appear in the console.

What just happened?

Imagine that the video was a physical object (like a phone), and it was connected to your speakers by a cable.

We unplugged that cable and connected the video to a new object called a gain node. Then we plugged the gain node into your speakers. Sound flows from the video to the gain node to the speakers. The gain node has a volume dial on it, and we can adjust that dial to amplify the sound.

Older