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: 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: Class Chatroom
We're going to give you some practice using async JS and also start introducing you to the infrastructure we're using for messaging. Hopefully by the end of class you will be able to send message to each other in the classroom!
We have prepared some basic starter code that will at first do messaging locally. We will simulate the time it takes to send and fetch messages to build out the appropriate UI. Then with some small changes, we'll plug in code to connect to the server and you will be able to to talk to each other!
Step 1: Basic Usability Improvements
Currently, if you test out the application, you
should be able to "send" a message and click the refresh
button to see the sent messages.
If you take a look at index.js
you'll
see the messages are just local variables.
We will eventually replace that with real functionality,
but to get started, lets make this "fake" chat app
a little more usable first.
Feel free to make more changes, but some things you should definitely do are:
- Safety:
- Disable the send button when the message text is empty.
- Efficiency:
- Clear the message once it has been sent
- Refresh the message list automatically when a message has been sent
Finally, also sort the messages to show the most recent ones first.
You can use the built-in toSorted
Array method.
You will need to pass a function to toSorted
that takes two
arguments, each members of the array you are trying to sort.
If the function returns a negative number, the first argument
will be sorted before the second argument, otherwise the second
argument will be sorted first. Sort over the published
property
of the message objects.
Step 2: Pretend Async
Before actually sending a message we can simulate what it will be like to have to wait for the messages to be sent or to be received.
Before this.sentMessageObjects.push()
, we're going
to add a timeout to simulate the time it takes to send a message.
The common way to do that is with the following Promise:
new Promise((resolve) => setTimeout(resolve, 1000));
First, try to parse what's going on with this promise:
its actually passing a function to a function within a function... what??
What is the argument of the Promise
constructor?
What type of variable is resolve
?
You can also read the documentation for setTimeout.
Once you have tried to wrap your head around it,
add the promise before your this.sentMessageObjects.push()
call.
Does it work? Probably not... remember to use the
async
and await
keywords!
Step 3: Loading...
Currently our app is a bit frustrating to use. When you click the send button nothing happens for a little bit and then suddenly things change. 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 Send button or next to it, would indicate that Sending 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 sending
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 4: Preventing bugs and race conditions
Currently, the user can still send messages while the message is being sent. This can lead to a user sending messages multiple times, 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.
Step 5: Simulating receiving messages
Try a similar technique to simulate waiting for
receiving messages.
Add the timeout promise within for-loop of the *getMessageObjectsIterator()
function - again, don't forget to add async/await
keywords!
Then, change your for loop in the getMessages()
function to
to a for await
loop.
Add loading indicators and disable buttons as appropriate.
Step 6: Messaging
Almost there!! We need to replace our "simulated" message functionality with real functionality.
For sending, we can swap out
this.sentMessageObjects.push()
with the following code.
It essentially does the same thing but instead
of adding your message to the local sentMessageObjects
array,
it adds it to a shared "channel" specified in the channels
variable.
await this.$graffiti.put(
{
value: {
content: this.myMessage,
published: Date.now(),
},
channels,
},
session,
);
For receiving, we can replace the this.getMessageObjectsIterator()
function with the following code, which discover
s all the messages
in the shared channel that have appropriate properties,
content
for the message content and published
for the timestamp.
const messageObjectsIterator = this.$graffiti.discover(channels, {
value: {
properties: {
content: { type: "string" },
published: { type: "number" },
},
},
});
Try it out and make sure it still works... then
finally change the graffiti
variable at the bottom of the
code to use GraffitiRemote
rather than GraffitiLocal
.
Log out and log back in with a Solid account.
Do you see your peer's messages?