6.4500 Design for the Web: Languages and User Interfaces

↑ All studios

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:

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 discovers 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?