Studio 9
Design critique of bookmarklets for hiding undesirable content
We have curated a few representative homework examples to discuss here.
Kerning game!
Play this game until the end: https://type.method.ac/ Record your score and share it with the class. Who got the highest score?
Decomposing emojis
You can find out how an emoji decomposes into its component characters with a single line of JS! This utlizes the spread operator to convert a string into an array of characters.
Play around with this a little bit: how many component characters does the most complex emoji you can find have?
Async practice: To-Do list that saves to GitHub
Remember the vanilla JS to-do list from studio 7 that you also worked on for HW7?
In this studio, we will practice async JS by making this to-do list store its data on GitHub, instead of the browser's local storage, so we can also view and edit its data on our other devices, without having to maintain separate to-do lists.
Our starting point will be the solution for studio 7, modified to save when a button is clicked, instead of automatically. You can find it in studio9-starter. As usual, please fork this repo, and clone your fork to your computer. Do not just clone the starter repo directly, as you will not be able to push to it, and for this exercise this will be necessary.
Step 1: Authentication UI
To be able to save to GitHub, our app needs to offer log in UI for that will allow the user to log in to GitHub. Let's add controls for that. You will need:
- A log in button
- A log out button
- An element that displays the user's GitHub username and avatar
You can add these before the Save button.
Now, not all of these controls should be shown at all times. The log out and save buttons, and the user info element should only be shown when the user is logged in. Similarly, the log in button should only be shown when the user is logged out.
To implmenent this, we will toggle a logged-in
class on the app container (the element with id="app"
),
then will use descendant selectors to apply display: none
to elements depending on the presence or absence of this class.
Add the logged-in
class manually and refresh to make sure your code works as expected in both states.
Step 2: Authentication functionality
Now, we need to hook these log in/out buttons to code so they work properly,
and handle adding and removing the logged-in
class when the user is actually logged in or out (using element.classList
).
We will use Madata for authentication and storage.
Import Madata and create a new Backend
object according to the instructions on its website.
For the URL, use "https://github.com/YOUR_USERNAME/studio9-starter/data.json"
(replace YOUR_USERNAME
with your GitHub username).
Add event listeners for the log in and log out buttons which call backend.login()
and backend.logout()
accordingly.
After the log in is successful, you should display the logged in user information (backend.user.username
and backend.user.avatar
will be useful for this).
Step 3: Handling passive authentication
When the user has previously logged in to Madata, Madata stores a token in the browser's localStorage
],
so it could authenticate them again on future page loads, without them having to click "Log in" and go through the login flow every time.
However, right now because we are only displaying user info when the log in button is clicked, the user appears logged out when the page is loaded.
To fix this, you need to decouple the code that calls backend.login()
and backend.logout()
from the code that updates the UI to display the info of the current user.
You should listen to the mv-login
and mv-logout
events on the backend
object,
and modify the UI when these are fired.
This will allow you to update the UI when the user is logged in and logged out, regardless of how they got there.
Step 4: Saving to GitHub
Modify the code that saves the to-do data to local storage, to use the backend.store()
method instead.
Add some entries, then save.
Step 5: Loading from GitHub
Reload your page. Where did your entries go?!
Well, we have not yet written any code that reads the data from GitHub, the code still reads from localStorage
.
Use the backend.load()
method to load the data from GitHub at startup.
Step 6: Indicating progress
Currently our app is a bit frustrating to use. When we load it, our data is not there, until eventually it is, but there is nothing indicating that loading is in progress. When we save, nothing seems to happen. To fix all these issues, we need to provide feedback to the user.
A common way to do that is a progress indicator, which is a visual cue that something is happening.
We have included a simple animated progress indicator in the starter code (img/loader.svg
, rendered as ).
Because it uses SVG, you can edit its code to change colors, etc.
Do note you will need to constrain its dimensions, as by default it occupies all available space.
The location of the progress indicator can further communicate what is happening. E.g. putting the progress indicator inside the Save button or next to it, would indicate that Saving is in progress. If the location of the progress indicator is not clear, you can add a label to it, e.g. "Loading data...".
If you display a progress indicator inside a button, take extra care not to have the button change size when the indicator is shown or hidden, which is a jarring user experience.
An easy way to implement this would be to toggle a loading
and saving
class on the app container (the element with id="app"
),
then use descendant selectors to apply display: none
to elements depending on the presence or absence of this class.
Step 7: Preventing bugs and race conditions
Currently, the user can interact with the entirety of the UI, even when certain things are in progress. E.g. the user can click the Save button while Saving is in progress, and they can add and edit entries while loading is in progress. This can lead to bugs and lost work, and thus is a safety issue. You want to prevent them from doing this, by disabling relevant parts of the UI when they should not be interacted with.
<fieldset>
elements allow you to group form elements.
Disabling a <fieldset>
element disables all form elements within it, which is very convenient in this case.
To avoid visually rendering the <fieldset>
element,
you can include this rule in your CSS:
fieldset {
display: contents;
}
display: contents
prevents an element from being rendered, but still allows its children to be rendered.