Write Less JavaScript
How many lines of JavaScript code were used to create the following interface?
At Mystery Science, we recently released this feature to capture feedback after a teacher has taught a lesson. There are four possible screens or states:
- Prompt (did you teach?)
- Rating (stars)
- Comments (open-ended feedback)
- Done
Each state (except the last one) prompts and subsequently saves user input. The server knows I taught a lesson as soon as I click “yes.” The star rating and comments work the same way — all responses are submitted via XHR. How little JS code can we write to get this feature working?
The first trick to writing less JS is to find the right abstractions. Let’s explore the two key abstractions here. When a teacher submits a response, JS is advancing the interface’s state. This can be accomplished in any number of ways but at the end of the day some elements are being shown (display: inherit
1) and others are being hidden (display: none
). JS also has to send the response to the server, which in jQuery-terms eventually means a call to $.ajax()
.
Let’s wire this up:
This is a great start! This code is readable and there’s not much of it. But we’ve introduced some event handlers like enterRatingState()
that are not defined. Presumably, these functions will reach into the container and show and hide their respective elements. But given that each state has many elements, how can we do this without introducing a bunch of JS? Let’s introduce some CSS to do the heavy-lifting. This is the second trick to writing less JS: defer view logic to CSS whenever possible.
Let’s unpack this. Obviously, .lesson-feedback
is the container. We’ll modify the container with .state-*
classes to enter and exit the four states. This means there will also be .state-prompt
, .state-comments
, and .state-done
. Each state needs blocks like the ones above. I’ve also included .visible-during-*
and .hidden-during-*
helper classes to control elements that span states (like the header and image). It may seem like a lot of CSS but once it’s there you shouldn’t need to change it unless you add or remove a state. Some of the repetition can also be reduced with a preprocessor like LESS or SASS.
Now, enterRatingState()
and the other state-related functions are really lightweight:
That’s all we need to drive the interface; looking good! Now, let’s look at data submission. In the first code example, I introduced submitViaXHR()
. Let’s define that now.
Remember, all the forms have submitViaXHR()
bound as an event handler on submit. Instead of submitting to the server with a traditional page refresh, jQuery steps in and submits the data via XHR instead — this is nothing new. The real elegance here is that they all submit the same way. There are not three different handlers for the prompt, rating, and comments forms. The third trick to writing less JS is to keep data in HTML. Here’s an example of the HTML for the first state, prompt:
There are two really lightweight forms with hidden values that submit to the server. That’s all we need. The destination of the request (/viewings
), the HTTP verb (PUT
), and the request data ({ viewing: { actually_taught: true} }
) are all stored in the markup and that’s a good thing.
So let’s recap. To write less JavaScript:
- Find the right abstractions.
- Defer view logic to CSS whenever possible.
- Keep data in the HTML.
1: inherit
is a better choice than block
for visible elements. This approach will allow inline elements to preserve their inline display type.