This is a summary of the key information I took away from each project.
- 1. Javascript Drum Kit
- 2. JS and CSS Clock
- 3. CSS Variables
- 4. Array Cardio Day 1
- 5. Flex Panel Gallery
- 6. Type Ahead
- 7. Array Cardio Day 2
- 8. Fun with HTML5 Canvas
- 9. Dev Tools Domination
- 10. Hold Shift and Check Checkboxes
- 11. Custom Video Player
- 12. Key Sequence Detection
- 13. Slide in on scroll
- 14. Javascript Reference vs Copying
- 15. LocalStorage
- 16. Mouse Move Shadow
- 17. Sort Without Articles
- 18. Adding Up Times with Reduce
- 19. Webcam Fun
- 20. Speech Detection
- 21. Geolocation
- 22. Follow Along Link Highlighter
- 23. Speech Synthesis
- 24. Sticky Nav
- 25. Event Capture, Propagation, Bubbling and Once
- 26. Stripe Follow Along Nav
- 27. Click and Drag
- 28. Video Speed Controller
- 29. Countdown Timer
- 30. Whack A Mole
What I learned on this mini-project.
window.addEventListener('keydown', playSound);
This is used to attach an event handler to the window object. An event in this context is when "something happens to the window object".
keydown
is the event nameplaySound
is the function that runs when the event happens
const key = document.querySelector(`.key[data-key="${event.keyCode}"]`);
const keys = document.querySelectorAll('.key');
This will return the HTML element you trying to select (or all of them as a NodeList), an example of what will be stored in key
is shown below.
<div data-key="65" class="key">
<kbd>A</kbd>
<span class="sound">clap</span>
</div>
key.classList.add('playing');
key.classList.remove('playing');
key.classList.append('playing');
This quite simply adds classes like you would in jQuery with key.addClass
.
key.addEventListener('transitionend', removeTransition)
You can listen for when a transition finishes, the length of the transition (in this case) was given in the following CSS transition: all .07s ease;
- NodeList
What I learned on this mini-project.
You can extract the second
, minute
and hour
of the date object by accessing it's object methods.
const seconds = now.getSeconds();
const minutes = now.getMinutes();
const hours = now.getHours();
This can be used to perform a function at a given interval, here, 1000 is equal to 1 second.
setInterval(setDate, 1000);
setData
- The method to run1000
- The interval to wait until running the method (again)
You can use dot notation to add styles to an element extracted from the DOM.
const secondHand = document.querySelector('.second-hand');
secondHand.style.transform = `rotate(${secondsDegrees}deg)`;
Here, the transform attribute (on the HTML element with the class second-hand
) has been given a dynamic value.
This CSS attribute can be used to determine where a given transform 'pivots' from.
transform-origin: 100%;
100%
- Moves the pivot from its default position (which is the center of the element i.e50%
) to the end of thex-axis
Useful for adding a custom transition effect.
transition-timing-function: cubic-bezier(0, 2.11, 0.58, 1);
You can use chrome dev tools to create your cubic-bezier
. More info on this here
- - Fix the transition judder at 0/60 seconds
What I learned on this mini-project.
Like with SASS, you can create variables for use throughout your stylesheet.
/* Definition */
--base: #ffc600;
/* Use */
.hl {
color: var(--base);
}
The prefix --
is necessary.
Note - Remember, that this can be overruled by capitalizing on the cascading property of CSS.
Can be used to access data attribute values given to HTML elements. These data elements must be prefixed with data-
. An example is the following...
<input id="spacing" type="range" name="spacing" min="10" max="200" value="10" data-sizing="px">
You can access this value with the following code...
const suffix = this.dataset.sizing;
- Figure out why the range slider doesn't show when using dev tools
What I learned on this mini-project.
This can be used to assess each item of an array against a condition, and create a new one for those that pass.
const fifteen = inventors.filter(inventor => (inventor.year >= 1500 && inventor.year < 1600));
Here, we used a more concise syntax. If the resulting boolean expression returns true
it will 'pass the condition' and be returned in the filtered array.
inventor
takes the role of the current iterand, much likei
in a traditionalfor (var i = 0; i++; i < arr.length)
loop
This is useful when you wish to apply array methods to a node list extracted from a website (in this case wikipedia).
const links = Array.from(category.querySelectorAll('a'));
Now, you are able to apply, map
, reduce
, sort
etc... to links.
This method takes an array and attempts to whittle it down to single value (or values shown in the next exanple).
const totalYears = inventors.reduce((total, inventor) => {
return total + (inventor.passed - inventor.year);
}, 0)
It's best to think of the parameters total
and inventor
as an accumulator
and iterand
. The 0
provides a starting point for the total, since if it were omitted, it would use undefined
to add the first iterand to.
Perhaps a more complex use case for reduce is when trying to reduce multiple elements in an array.
The goal was to reduce each element to the number of occurrences in the array.
const transportation = data.reduce((obj, item) => {
if (!obj[item]) {
obj[item] = 0;
}
obj[item] ++;
return obj;
}, {});
The key in this case, was to use an empty object
as the initial value for the object, and call obj[key]
for each iteration.
What I learned on this mini-project.
You can toggle a class with the following code. This will respond to an event via an event listener. This is commonly used in click events (which is what its used for in this case).
this.classList.toggle('open');
this
refers to each separate panel when clicked on
When listening for transitionend
, the event might return multiple elements that were affected. Utilizing propertyName
you can respond to a element with more specificity.
event.propertyName.includes('flex');
includes()
- Used here, since IE explorer refers to the flex property asflex-grow
- Complete What the Flexbox
What I learned on this mini-project.
In this case, fetch is used to retrieve data from a url which results in a JSON data source.
fetch(endpoint).then(blob => blob.json());
This will return a promise, that can be resolved. The json()
method can be used to retrieve the data.
The following code will add the data to an array
fetch(endpoint).then(blob => blob.json()).then(data => cities.push(...data));
Note Use of the ... spread
operator to push each data object into the array instead of it entirely (which would push the entire data source at index 0
)
In order to use a variable
as your string to match, you need to create a RegExp
object.
const regex = new RegExp(wordToMatch, 'gi');
This can then be used in match()
methods, for example
return place.city.match(regex);
What I learned on this mini-project.
Checks against the array, and returns true
if at least one item meets the condition specified.
const isAdult = people.some(person => (new Date()).getFullYear() - person.year >= 19);
new Date()).getFullYear()
gets the current year
Checks against the array, and returns true
if all items meets the condition specified.
const allAdults = people.every(person => (new Date()).getFullYear() - person.year >= 19);
new Date()).getFullYear()
gets the current year
Checks against the array, and returns the result of the item, that meets the criteria (in this case it is an object)
const index = comments.find(comment => comment => comment.id === 823423);
Checks against the array, and returns the index
of the item that meets the condition specified.
const index = comments.findIndex(comment => comment.id === 823423);
What I learned on this mini-project.
The canvas has properties that can be modified to alter the line style.
const ctx = canvas.getContext('2d');
ctx.strokeStyle = '#BADA55';
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
ctx.lineWidth = 10;
ES6 allows you to easily name multiple variables, demonstrated by the the following code
[lastX, lastY] = [event.offsetX, event.offsetY];
What I learned on this mini-project.
You can use %c
to style the text you logged to the console.
console.log('%c This is blue by using percent c', 'color: blue');
You can also, log warning, errors and information to the console much like you would with regular text.
// warning!
console.warn('Warning');
// Error :|
console.error('ERROR!');
// Info
console.info('Crocodiles eat 3-4 people per year!');
If you wish to see an elements properties and methods in a cleaner form, the following code will make this easier
console.dir(p);
You can use use assertions in the console, the following example demonstrates checking to see if an element has a certain class.
const p = document.querySelector('p');
console.assert(p.classList.contains('ouch'), 'That is wrong!');
One useful feature for neatening the output on the console is groupCollapsed
Using this along with groupEnd
ensures that the data for each list item will be concise and separate.
dogs.forEach(dog => {
console.groupCollapsed(`${dog.name}`);
console.log(`This is ${dog.name}`);
console.log(`${dog.name} is ${dog.age} years old`);
console.groupEnd(`${dog.name}`);
});
What I learned on this mini-project.
Using let for variable assignments is useful when you expect the the variable to change.
let lastChecked;
This is different to const
which is for variables you wish to remain constant throughout the script.
You can use the shiftKey property of an event to check if its active
event.shiftKey;
In this case, this is used for a click
event
To check whether a checkbox has been checked or not, you can assess its checked
property (I heard it too...)
checkbox.checked;
What I learned on this mini-project.
You can access the video
element and manipulate its behavior via it's properties and methods.
To assess whether or not the video is paused, the following property can be checked.
// returns a boolean
video.paused;
To play and pause the video you, can call play
and pause
methods on the video object, like so
const method = video.paused ? 'play' : 'pause';
video[method]();
To move along a video you can change its currentTime
property.
video.currentTime += 10;
In this example, we had to assess how far along the cursor was for a horizontal range element, offsetWidth
gives the length of the element, whereas offsetX
gives the amount the cursor is along the x axis (of the element).
The code to get the time of the video relative to the scrubbed amount is shown below.
const scrubTime = (event.offsetX / progress.offsetWidth) * video.duration;
This is a useful event that is fired when the video is playing (as in the time that the video has been playing is updated), in this case it was used to fill the progress bar proportionately.
video.addEventListener('timeupdate', handleProgress);
- Complete the fullscreen functionality
What I learned on this mini-project.
When listening for a keyup event, you can access the name of the key being pressed.
event.key;
This will return, for example Shift
if the shift key was pressed
If you are trying to match user input, you may need the last pressed x
digits, a cool way of doing this is to add their input to an array and apply a splice
followed by join
.
let pressed = [];
pressed.push(event.key);
pressed.splice(-secretCode.length - 1, pressed.length - secretCode.length);
The splice in this case
- Starts at the end of the array
- Ends when the length of the
secretCode
has been met
What I learned on this mini-project.
When you want to manipulate the DOM on scroll, it's important to limit the number of events that elicit a response.
function debounce(func, wait = 20, immediate = true) {
var timeout;
return function() {
var context = this,
args = arguments;
var later = function() {
timeout = null;
if (!immediate)
func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow)
func.apply(context, args);
};
};
The debounce method will wait 20ms before registering an event, which will reduce browser overhead
The window (and other objects) have plenty of properties associated with scrolling activity that can be viewed and manipulated.
window.scrollY
window.innerHeight
sliderImage.offsetTop
Properties above represent different values updated according to scrolling behavior.
What I learned on this mini-project.
Javascript will make copies of these types, so changing the values like below is okay
let age = 100;
let age2 = age;
age = 200;
In this case, the age2
variable will keep its original value of 100
When dealing with arrays, you need to be aware of the concept of references.
const players = ['Wes', 'Sarah', 'Ryan', 'Poppy'];
const team = players;
Here, modifying the team
array's values will actually modify the players
array, for example
team[3] = 'Lux';
console.log(players[3]); // 'Lux'
To leave the original array intact, you must make copies of it. There are multiple ways of doing this, some are shown below
const team2 = players.slice();
const team3 = [].concat(players);
const team4 = [...players];
const team5 = Array.from(players);
The same principle of referencing (from arrays) apply here. To copy an object to another variable, the following code would work
const person = {
name: 'Wes Bos',
age: 80
};
const cap2 = Object.assign({}, person, { age: 99 });
The Object.assign()
method takes in the following paramters...
target
- In this case, this is a blank object{}
sources
- Object(s) to add
Note
{age: 99}
- This will modify the existing age variable in person
What I learned on this mini-project.
This is useful if you want to stop a form submitting, which by default, will redirect you to the form handling page.
event.preventDefault();
A really cool feature implemented in this project, was the use of a ternary operator to dictate whether or not an attribute was given to some HTML.
<input type="checkbox" data-index=${index} id="item${index}" ${plate.done ? 'checked' : ''} />
Here, the ternary operator would either add checked
or nothing, dictated by
the boolean value returned from plate.done
When attaching event listeners to elements, you might want to access data from them.
In the case of form elements, with inputs
and labels
etc, you
might not be able to distinguish which was clicked.
Using event.target, you can narrow down to which element is needed.
if (!event.target.matches('input')) return; // skip
This example, will return all elements clicked other than input's
'
When you wish to attach an event listener to an element that doesn't exist on the page, at the time of loading, event delegation
is needed.
It works by attaching the event listener to a parent element (that does exist), which then passes on the instruction (delegates) to the child element being targeted.
itemsList.addEventListener('click', toggleDone);
function toggleDone(event) {
if (!event.target.matches('input')) return; // skip
const element = event.target;
const index = element.dataset.index;
items[index].done = !items[index].done;
localStorage.setItem('items', JSON.stringify(items));
populateList(items, itemsList);
}
itemsList
is the parent element, that delegates the instruction to the
event.target
elements.
Browsers, now have the ability to store local data for individual webpages and browsers. The data is stored as a string that can be parsed into javascript objects.
You can set and get items from localStorage
.
localStorage.setItem('items', JSON.stringify(items));
Using JSON.stringify()
here is necessary, as it allows you to convert the object’s key value pairs to it’s string representation.
An example is shown below, which demonstrates utilizing the data and adding it to a variable to use between browser reloads.
This enables your pages to have continuity based on existing user behavior.
const items = JSON.parse(localStorage.getItem('items'));
Here, the 'items'
refer to the string interpretation of the object,
whilst JSON.parse
converts the string back to a javascript object, ready for
use.
What I learned on this mini-project.
This is a neat way of assigning multiple key:value
pairs to a single
object.
const { offsetWidth: width, offsetHeight: height } = hero;
It can be a little confusing at first. In this case
hero.offsetWidth; // width
hero.offsetHeight; // height
When you're looking at coordinates based on an event listener, you have to consider the position of the element being triggered upon.
In this case, the coordinates will change when a new target is hovered over.
Adding the elements offsets from the targeted element can counteract this. An example is shown below
if (this !== event.target) {
x = x + event.target.offsetLeft;
y = y + event.target.offsetTop;
}
The event in this case is attached to a div
, and the event.target is a child element.
What I learned on this mini-project.
Replacing words through regular expressions can be done via the replace
method.
bandName.replace(/^[a |the |an ]/, '').trim()
In this case, we are targeting a
, the
or an
.
The spaces are used to prevent all words beginning with these sequences of letters being replaced.
Trim removes whitespace from the end of the string.
^
Targets the start of the string|
Or operator
If a function's only task is to return something, then it's best to use implicit returns, which allows you to omit return
.
(a, b) => strip(a) > strip(b) ? 1 : -1
This example uses a ternary operator
, which is designed to return either 1
or -1
.
What I learned on this mini-project.
If you give elements a data element, in the form data-x
you can extract them through attribute selection, this is shown below.
const timeNodes = Array.from(document.querySelectorAll('[data-time]'));
Accessing the values for this particular case, can be done via timeNodes.dataset.time
Numbers are extracted from HTML elements in this project, but they are stored as strings. Using this method
you can convert them to the numeric type
. This is useful for performing mathematical operations.
const [mins, secs] = timeCode.split(':').map(parseFloat);
In this case, the method has been applied through map
.
What I learned on this mini-project.
The source of your video is in the form of a URL, that can be generated by the
navigator
object. This is done by accessing it's property mediaDevices
which in turn calls a property getUserMedia
.
navigator.mediaDevices.getUserMedia({ video: true, audio: false });
The result of this is a promise
that when successful, is used to generate
the URL.
video.src = window.URL.createObjectURL(localMediaStream);
Browsers interpret images as a series of pixels, where each pixels' rgba
values that are stored in a large two dimensional array of millions of values.
In these arrays, every pixel has four entries, one for each rgba
value. So for example
[0, 1, 2, 3]
Would mean a red value of 0, green value of 1 etc...
In this case, the pixels are drawn onto the canvas via drawImage
which takes in the following parameters
video stream
starting x coordinate
starting y coordinate
width
height
This method provides the same functionality as prepend
from jQuery.
strip.insertBefore(link, strip.firstChild);
Here, strip
represents a div element.
What I learned on this mini-project.
This is a window object that lets you use your microphone to implement speech recognition functionality
window.SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
const recognition = new SpeechRecognition();
In chrome, you need to prepend SpeechRecognition with webkit
.
This is the event that can be listened for when the microphone receives input.
recognition.addEventListener('end', recognition.start);
The result of this, is a SpeechRecognitionResultList
object which has
many properties, one of which is the transcript
containing the words that
were interpreted.
Another property from this object, that was used here is isFinal
, which attempts to detect the ending of the speech.
What I learned on this mini-project.
This is a property of the window object, that contains data about the users location. You must grant access for it to work.
navigator.geolocation.watchPosition(data => {
console.log(data);
speed.textContent = data.coords.speed;
arrow.style.transform = `rotate(${data.coords.heading}deg)`;
}, (error) => {
console.error(error);
alert('ENABLE GEOLOCATION!!!');
});
The geolocation object is only introduced very briefly.
This method, allows you to update the users position at regular intervals, instead of getting a snapshot of where they are.
The data returned then needs to be manipulated via a promise
, that handles
success and failure outcomes.
Most of the data that you will need is contained in this object. Examples of this include the latitude, longitude and heading (representing degrees from north).
What I learned on this mini-project.
Used to return an elements size and position relative to the window.
const linkCoords = this.getBoundingClientRect();
const coords = {
width: linkCoords.width,
height: linkCoords.height,
top: linkCoords.top + window.scrollY,
left: linkCoords.left + window.scrollX,
}
It returns an object, which represents the CSS borders associated with
the element. The values returned are read-only and can not be updated these include left
, top
, x
and y
.
Values represent the border-box
in pixels and are relative to the top-left
of the viewport (this doesn't apply to width
and height
values).
When the scrolling position changes, the values returned above will adjust to
be bound to the current top-left
of the viewport. So, to make sure
the values are bound to the top-left
of the document, you need to add scrollX
and scrollY
, shown below.
let top = linkCoords.top + window.scrollY;
let left = linkCoords.left + window.scrollX;
What I learned on this mini-project.
This is an interface for the Web Speech API. It contains the content that the speech service will read, as well as options that can be configured, such as language
and pitch
.
const msg = new SpeechSynthesisUtterance();
// Example properties used
msg.lang;
msg.pitch;
msg.rate;
msg.text;
voiceschanged
- This event listener can be used to attach an event to add voices to use.getVoices()
- Will extract the voices on the device being used as an array
This is the controller interface for the speech service, which can be used to get information about the voices available, on your particular device. It can also be used to start and pause the speech.
speechSynthesis.paused;
speechSynthesis.pending;
speechSynthesis.speaking;
speechSynthesis.cancel();
speechSynthesis.pause();
speechSynthesis.speak();
Above, are example properties and methods that can be used to alter the state of the speech device.
At times you might want to provide a parameter to a function for use in an event listener. Unfortunately, doing this by a traditional function call will result in it being run on page load.
Using an anonymous function, you can avoid this problem
stopButton.addEventListener('click', () => toggle(false));
One caveat to this is that a new function will be created. Relying on this approach can cause unnecessary overhead to your code, which could have negative implications on your websites speed and efficiency.
What I learned on this mini-project.
The main purpose of this project was to learn about the effects of changing an elements position to fixed
.
When applying this property, the element is essentially taken out of the DOM
which vacates the space it previously held in it.
This causes a reshuffle for the other elements, which can cause a judder when an elements position is changed to fixed.
.fixed-nav nav {
position: fixed;
box-shadow: 0 5px rgba(0, 0, 0, .1);
}
One workaround to this, is to fill in the space vacated by adding padding
.
This can be done multiple ways, but it makes sense to make this value
dynamic, so that it adapts when the layout of the page is changed.
document.body.style.paddingTop = `${nav.offsetHeight}px`;
What I learned on this mini-project.
When an event listener has been attached to an element, that has parents with the same listener, triggering the event will lead to all elements registering.
By default the events will be triggered from the inside out, but
setting capture
to true
will reverse this direction to outside in.
divs.forEach(div => div.addEventListener('click', logText, {
capture: false
}));
This is a useful option for the addEventListener
method, that will
prevent the element from triggering multiple events. It has the same
functionality as removeEventListener
.
divs.forEach(div => div.addEventListener('click', logText, {
once: true
}));
This is the blanket term that refers to bubbling
and capturing
. It
essentially means that events will cascade up and down the document
object model from the event target
to the window
object.
The direction of propagation can be both ways.
A nice summary of the concept is explained by dividing it into three phases.
This was taken from the following article.
capture phase
- From the window to the event targettarget phase
- The event targetbubble phase
- From the event target back to the window
What I learned on this mini-project.
This is used when you wish to match direct descendent elements.
const triggers = document.querySelectorAll('.cool > li');
So, in this case the first li
tag, of elements with a class of cool
will be selected.
In this project, an element needs to be displayed and faded in. To begin with, the element is given the following CSS... display: none
, an unfortunate consequence of this, is that transitions can't be applied elements that aren't displayed.
By using multiple classes and adding CSS sequentially, you can first add the element to the page through a class with display: block
, then add a second class with the desired transformation properties.
The following code demonstrates this.
this.classList.add('trigger-enter');
setTimeout(() => this.classList.contains('trigger-enter') && this.classList.add('trigger-enter-active'), 150);
Note, a delay of 150ms has been added before adding the second class, this is to maintain the order in which the classes are added.
This object, will by default, give positional values based on the current viewport's top-left
point (which corresponds to (0, 0)
).
On occasion however, you might want the element you are targeting to be based on another element.
To do this, you must get that element's positional values via getBoundingClientRect
and subtract these from the target element's positional values.
An example is shown below, where the dropdown
is positioned based on the 'nav'.
const navCoords = nav.getBoundingClientRect();
const dropdownCoords = dropdown.getBoundingClientRect();
const coords = {
height: dropdownCoords.height,
width: dropdownCoords.width,
top: dropdownCoords.top - navCoords.top,
left: dropdownCoords.left - navCoords.left
};
What I learned on this mini-project.
These can be used to control the flow of a method or other piece of functionality.
They are mainly used to hold a value until some condition changes, after which you can modify the variable. One of the most common use cases for this is holding a boolean which is dependent on some other piece of code.
let isDown = false;
isDown = true;
In this example, the isDown variable is used to track the state of the mouse. When the mousedown
event is triggered the isDown
variable is changed, which is used to add further functionality.
In this project, the mousedown
event is used to initiate the click and drag functionality. For this to work effectively, an anchor point was used, representing the initial click point, the pageX
property of the event captures this value.
event.pageX
If a margin or padding is added, then the pageX
value will be off, to compensate for this adjustment, you can subtract the pixels changed (from the parent element's offset) to the window.
The code to do this, is shown below
startX = event.pageX - slider.offsetLeft;
What I learned on this mini-project.
This property represents the height of an element, including vertical padding and borders. It is measured in pixels
and is returned as an integer.
const percent = y / this.offsetHeight;
In this case, offsetHeight
was used in the percentage calculation used to fill the speed-bar.
It made sense for the speed-bar to have appropriate minimum and maximum values, to achieve this, normalization was used, allowing us to map the percentage scrolled, to these minimum
and maximum
values.
const playBackRate = (percent / (max - min)) + min;
In this case, minimum = 0.4
and maximum = 4
. So, the percentage scrolled would fall between the two. For example 10%
is approximately equal to a playBackRate of 0.43
.
What I learned on this mini-project.
The setInterval
method has been used in previous projects, but in this
case it's used a little differently, in conjunction with clearInterval
.
When you wish for a function or code piece of code to run every so often,
setInterval
can be used.
countdown = setInterval(() => {
// method
const secondsLeft = Math.round((then - Date.now()) / 1000);
}, 1000); // time delay
Here, an anonymous function which defines a constant is ran every
1000 milliseconds
.
In order to clear the countdown
variable i.e stop the method running
via setInterval
you need to clear it. If you don't do this, and
try to run the method whilst it's already running (through another
function call), they will both run, which can cause erratic behavior.
clearInterval(countdown);
Here, the method being executed under a given time interval is assigned to countdown
, so clearing the variable does the job of cancelling the method calls.
If you want the current time, then Date.now()
can be used, this
will return the number of milliseconds elapsed since 01/01/1970
.
It's easy to turn this into a date object via new Date(milliseconds)
,
where the value extracted from Date.now()
is used as a parameter.
Using this object, various methods can be called, to extract different aspects of the time data such as the day, month and year.
const timestamp = Date.now();
const end = new Date(timestamp);
const hour = end.getHours();
const minutes = end.getMinutes();
When you want to attach an event to a form, you can utilize this
to
extract it's input values.
document.customForm.addEventListener('submit', function (event) {
const mins = this.minutes.value;
});
Here, an event listener is attached to the form via its name
attribute customForm
,
and its inputs are extracted by accessing properties off this
.
What I learned on this mini-project.
Here, the click
event is important in determining the outcome of the game, therefore we need to protect from false clicks that can be be mimicked through cheeky javascript code.
Using the isTrusted
property allows you to confirm that the event was generated by a user action.
if (!event.isTrusted) return; // cheater
The code above, will return if the event was not a product of user action (not through a script).
This technique was used to prevent the same hole from being used twice in a row. It's important to return the value in the randomHole
method, so that it can be used recursively.
let lastHole;
function randomHole(holes) {
const idx = Math.floor(Math.random() * holes.length);
const hole = holes[idx];
if (lastHole === hole) {
return randomHole(holes);
}
lastHole = hole;
return hole;
}
Here, the lastHole
variable will be assigned to the randomly generated hole. If the following call generates the same hole, the randomHole
will be called again until lastHole != hole
.
This course does a really good job, of providing real world examples of javascript. This will definitely help you to retain information better than simply reading.
I highly recommend the course, so take it, if you have the time!