Luke Singham

A Review and Summary of the Javascript30 Course

πŸ“šCourse: Javascript30
πŸ’°Price: Free
⭐️Rating: 8.5/10

Review #

What could be better? Not a lot. I think Wes (the instructor) could have thought harder about prompting learners to pause the video and trying solo for themselves. He does once or twice somewhere in the first 10 lessons. But didn't do it after, which meant I tended to follow along waiting for the prompt. There are a couple of mistakes here and there - one was on a resource Wes was hosting returning a 404 for one of the lessons, but after emailing Wes fixed it about a week later.

I thought some items could have been explained better e.g. CSS query selectors and the different syntax to use. For someone with some programming experience and understanding who wants to focus on improving JavaScript skills, working with the APIs and developing with the browser this is a fantastic course. A couple of lessons are out-of-date however the finished solutions available in the repo are mostly up-to-date. I really like the honesty in the mistakes and even debugging his lesson code on video - it gives a much more honest representation of what programming is like. If a JavaScript jedi like Wes makes mistakes and spends time debugging so too will you.

01 - JavaScript Drum Kit #

window.addEventListener('keydown', function (e) {
const audio = document.querySelector(`audio[data-key="${e.keyCode}"]`);
});
<input id="spacing"... data-sizing="px">
function handleUpdate() {
const suffix = this.dataset;
}

inputs.forEach(node => node.addEventListener('change', handleUpdate));

02 - Clock #

03 - CSS Variables #

const inputs = document.querySelectorAll('.controls input');

function handleUpdate() {
const suffix = this.dataset.sizing || '';
document.documentElement.style.setProperty(`--${this.name}`, this.value+suffix);
}

inputs.forEach(node => node.addEventListener('change', handleUpdate));
inputs.forEach(node => node.addEventListener('mousemove', handleUpdate));

04 - array cardio day 1 #

Array methods map(), reduce(), reduce()  and  filter() are introduced.

console.table(sorted);

05 - Flex panels #

// grab relevant elements
var panels = document.querySelectorAll('.panel');

// function to toggle
function toggleOpen(e) {
this.classList.toggle('open');
}

// event listener to toggle
panels.forEach(x => x.addEventListener('click', toggleOpen));

06 - Type Ahead #

Implement a search box against json data.

const cities = [];
fetch(endpoint)
.then(blob => blob.json())
.then(data => cities.push(...data))
// (g)lobal, (i)nsensitive
const regex = new RegExp(wordToMatch, 'gi');
return place.city.match(regex)

07 - array cardio day 2 #

Some extra array methods

08 - Fun with HTML5 Canvas #

Could have provided more context on the why of canvas vs alternatives of divs, could have been more structured as well. What is canvas? What are its features? etc

<body>
<canvas id="draw" width="800" height="800"></canvas>
<script>
const canvas = document.querySelector("#draw");
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx.strokeStyle = '#BBDA55';
</script>

09 - Dev Tools Domination #

Depending on how familiar one is with the dev tools, some very good tips to be had

console.groupCollapsed('my group')
console.log('part1 collapsed')
console.log('part2 collapsed')
console.groupEnd('end of my group')

10 - Hold Shift and Check Checkboxes #

I found this one hard - and then returned to it after a 7mth break 😱

// select the divs
const checkboxes = document.querySelectorAll('.inbox input[type="checkbox"]');

function handleCheck(e) {
// check if shiftkey pressed AND box is checked
let inBetween = false;
if (e.shiftKey && this.checked) {
// loop through each checkbox
checkboxes.forEach(checkbox => {
// these are the outer bounds of the selection
if (checkbox === this || checkbox === lastChecked) {
inBetween = !inBetween;
}
// whilst in between is true - check those boxes
if (inBetween === true) {
checkbox.checked = true;
}
});
}
lastChecked = this;
}

// add event listener to handle clicks
checkboxes.forEach(checkbox => checkbox.addEventListener('click', handleCheck) );

11 - Custom Video Player #

12 - Key Sequence Detection #

// Add this script tag
<script type="text/javascript" src="https://www.cornify.com/js/cornify.js"></script>
...
// Then call this function to put corny shit on your webpage
cornify_add()

13 - Slide in on Scroll #

Tip - At this point I'm getting a little tired of the CMD+S to save in VS Code, CMD+TAB to switch applications to the browser and then CMD+SHIFT+R to hard refresh. The Live Server extension solves that problem - as soon as you save, the page reloads in the browser you launch with it.

if (conditions) {
image.classList.add('active');
} else {
image.classList.remove('active');
}

Simplified CSS

/* take half a second to transition,  initial set to invisible*/
.slide-in {
opacity: 0;
transition: all .5s;
}

/* on active, set to invisible*/
.slide-in.active {
opacity: 1;
}

14 - JavaScript References VS Copying #

// const
const age = 100;
const age2 = age;
age2 = "new"; // πŸ›‘ TypeError: invalid assignment to const

// both var + let behave the same
let num = 100;
let num2 = num;
num2 = 200;
console.log(num, num2) // πŸ‘ 100 200
// Make a copy of an array
const players = ['Wes', 'Sarah', 'Ryan', 'Poppy'];
const team = players;
team[1] = "new";
console.log(players, team)
// [ "Wes", "new", "Ryan", "Poppy" ]
// [ "Wes", "new", "Ryan", "Poppy" ]
// πŸ›‘ changes both `team` and `players` because `team` is just a pointer to `players`

// solution
const players = ['Wes', 'Sarah', 'Ryan', 'Poppy'];
const team = players.slice();
team[1] = "new";
console.log(players, team)
// [ "Wes", "Sarah", "Ryan", "Poppy" ]
// [ "Wes", "new", "Ryan", "Poppy" ]
// πŸ‘ could also do `const team = [...players];`
// with Objects
const person = {
name: 'Wes Bos',
age: 80
};

// attempt to make a copy:
const captain = person;
captain.number = 99;
console.log(person)
// Object { name: "Wes Bos", age: 80, number: 99 }
// πŸ›‘ this has modified the original

// solution
const captain2 = Object.assign({}, person)
captain2.number = 100;
console.log(person)
// Object { name: "Wes Bos", age: 80, number: 99 } πŸ‘

15 - Local Storage and Event Delegation #

N.b - this one is big, video is over 30mins.

// args are key value
localStorage.setItem('items', JSON.stringify(myObject))

16 - CSS Text Shadow Mouse Move Effect #

const { offsetWidth: width, offsetHeight: height} = hero
let { offsetX: x, offsetY: y } = e;

// uses this if statement to handle when the target is the whole screen, to then add on the screen offset
if (this !== e.target) {
x = x + e.target.offsetLeft;
y = y + e.target.offsetTop;
}

Much easier to use the following:

let { clientX: x, clientY: y} = e;
const walk = 100 //px
const xWalk = (x / width * walk) - (walk / 2);
const yWalk = (y / height * walk) - (walk / 2);

text.style.textShadow = `${xWalk}px ${yWalk}px 0 rgba(255,0,255,0.7)`

17 - Sorting Band Names without articles #

N.b. An 'article', being one of the grammatical articles. In this coding example, The definite article 'the' or the indefinite articles 'an' or 'a'.

let sorted = bandsArray.sort((a,b) => {
if (strip(a) > strip(b)) {
return 1
}
if (strip(a) < strip(b)) {
return -1
}
return 0
})
document.querySelector('#ulDivId').innerHTML =
sortedArray.map(item =>
`<li>${item}</li>`
).join('');

18 - Tally String Times with Reduce #

N.b. simple if you have some knowledge of map reduce functions.
My answer pretty similar to Wes's except instead of using a forEach to append the values into an array, he used a much nicer const timeNodes = Array.from(document.querySelectorAll('[data-time]'));. My answer:

const videos = document.querySelectorAll('li')
const times = [];

videos.forEach(vid => {
times.push(vid.dataset.time)
});

const totalTime = times.map(x => {
const [min, sec] = x.split(':');
return Number(min) * 60 + Number(sec);
}).reduce((acc, val) => {
return acc + val;
})

const mins = Math.trunc(totalTime / 60);
const secs = totalTime % 60;
console.log(mins, ':', secs)
// 298 : 58 - I should handle hours but meh πŸ€·β€

19 - Unreal Webcam Fun #

N.b. This was a slower start with a few issues to iron out cover in the first 3 bullet points...

<!-- 404 -->
<audio class="snap" src="https://wesbos.com/demos/photobooth/snap.mp3" hidden></audio>
<!-- replace with a local sound file -->
<audio class="snap" src="camera_sound.mp3" hidden></audio>
function getVideo() {
navigator.mediaDevices.getUserMedia({
video: true,
audio: false
}).then(localMediaStream => {
video.srcObject = localMediaStream;
video.play();
})
.catch(
err => {
console.error("Whoops Error: ", err);
});
}
console.log(pixels);
debugger

20 - Native Speech Recognition #

N.b. This was definitely a frustrating first 5mins of the tutorial, where if you're following along you don't get anything like what Wes is getting. This is because he is demonstrating the finished app on the left half of the screen whilst coding from scratch the app. It's not until 5mins when switches that he realises that it's not working.

const recognition = new SpeechRecognition();
recognition.interimResults = true;
recognition.lang = 'en-US';

let p = document.createElement('p');
const words = document.querySelector('.words')
words.appendChild(p)

recognition.addEventListener('result', e => {
const transcript = Array.from(e.results)
.map(result => result[0])
.map(result => result.transcript)
.join('')

p.textContent = transcript;
// create a new paragraph if the speaking has finished
if (e.results[0].isFinal) {
p = document.createElement('p')
words.appendChild(p);
}
console.log(transcript); // the results
})

recognition.addEventListener('end', recognition.start)
recognition.start()

21 - Geolocation based Speedometer and Compass #

// the request to use the geolocation of the client will ask for permissions from user
navigator.geolocation.watchPosition((data) => {
console.log(data) // see the data object

// update dom elements with the data
speed.textContent = data.coords.speed;
arrow.style.transform = `rotate(${data.coords.heading}deg)`
}, (err) => {
// if they reject the permissions
console.log(err)
alert("you privacy nut")
}

N.b. lesson applies an element that hovers over the link the mouse is currently positioned over.

const triggers = document.querySelectorAll('a');
triggers.forEach(a => {
a.addEventListener('mouseenter', addHighlight)
}
function addHighlight(e) {
const domDimension = this.getBoundingClientRect();
}
const highlight = document.createElement('span');
highlight.classList.add('highlight');
document.body.appendChild(highlight);

function addHighlight(e) {
...
highlight.style.width = `${domDimension.width}px`
highlight.style.height = `${domDimension.height}px`
highlight.style.transform = `translate(${domDimension.x}px,${domDimension.y}px)`
}

23 - Speech Synthesis #

N.b. - Firefox wasn't returning the voice objects from speechSynthesis.getVoices() at page load - switched to Chrome for this one.

msg.voice = voices.filter(v => v.name === this.value)[0]
// vs find
msg.voice = voices.find(v => v.name === this.value)

24 - Sticky Nav #

Fix a navbar to the top as you scroll down.

const navbar = document.querySelector('#main');
// top of navbar location
const navHeight = navbar.offsetTop;

function moveNavBar(e) {
const scrollAmt = window.scrollY;

if (scrollAmt >= navHeight) {
document.body.classList.add('fixed-nav');
document.body.style.paddingTop = `${navbar.offsetHeight}px`

} else {
document.body.style.paddingTop = 0
document.body.classList.remove('fixed-nav');
}
}

document.addEventListener('scroll', _.debounce(moveNavBar, 10));

25 - Event Capture, Propagation, Bubbling and Once #

This is a very quick practical on the concept of bubbling up and propagation. Requires some extra research if new to the concept.

divs.forEach(div => {
div.addEventListener('click', logText, {
capture: true, // 'trickle' down events
once: true // removeEventLister - unbind once event occurs once
})
});

26 - Stripe Follow Along Dropdown #

Extension of 22.

const triggers = document.querySelectorAll('.cool > li')
const background = document.querySelector('.dropdownBackground')

function handleEnter() {
background.classList.add('open')
this.classList.add('trigger-enter');
this.classList.add('trigger-enter-active');

// don't forget you can querySelector on `this`
const dropdown = this.querySelector('.dropdown');
const dropdownDim = dropdown.getBoundingClientRect();
background.style.setProperty('width', `${dropdownDim.width}px`)
}

function handleLeave(params) {
this.classList.remove('trigger-enter', 'trigger-enter-active')
}

triggers.forEach(trigger => trigger.addEventListener('mouseenter', handleEnter));
triggers.forEach(trigger => trigger.addEventListener('mouseleave', handleLeave));

27 - Click and Drag to Scroll #

// πŸ›‘ errors
let yourVar = true;
let yourVar = false;

// βœ… happy days
let yourVar = true;
yourVar = false;
const yourVarName = 10;
// slow way
console.log('yourVarName', yourVarName);
// fast way
console.log({yourVarName});

28 - Video Speed Controller UI #

This is a small lesson at ~9mins and doesn't introduce any new content.  Mostly a simpler version of Lesson 11.

29 - Countdown Clock #

Not a lot new in this one - but it is a fun use of the skills learnt to date in making a countdown clock.

<form name="customForm" id="custom">
<input type="text" name="minutes" placeholder="Enter Minutes">
</form>
document.customForm.addEventListener('submit', function(e){...})
// Nested elements can be accessed too
document.customForm.minutes.addEventListener('onhover', function(e){...})

30 - Whack A Mole Game #

As the title suggests this is a bit of a fun simple game that is using the concepts learnt in previous lessons - a good way to end the course.



✍️ Want to suggest an edit? Raise a PR or an issue on Github