Building AVR, Part 3: JavaScript Architecture

Once AIM was built, we needed a product to display the inventory that had been managed. The AutoConX Vertical (Responsive), or AVR, is a white-label product that allows publishers (newspaper or magazine) list inventory from sellers in their area. It’s a digital classified system that offers a lot of customization and flexibility.

This project was trickier than AIM because AIM was a brand new product. AVR, however, had to be a modern and responsive site that met all the publishers’ expectations from the legacy product. Publishers wouldn’t switch unless they saw real value in the new system. We had to build a product that we would put our own products on.

This is the story of the JavaScript architecture for AVR.

Note! If you’ve read my post about AIM JavaScript Architecture, this will mostly be a repeat. Skip to the difference.

I’m a strong proponent of progressive enhancement. AIM was built with that principle in mind and so was AVR. None of the core functionality on the site relies on JavaScript to work. That said, there are quite a few enhancements that give the user a much richer experience.

How is the JS organized? Our scripts are broken into page handlers that load additional modules that fulfill one task. We use RequireJS to asynchronously load those JavaScript modules. The general pattern is to search for a CSS class prefixed with `js-` and to require those additional modules if the selector exists.

AVR benefits from a couple of special and reusable modules. One is the Carousel. In its simplest form, the Carousel is like a slideshow. It shows a configurable number of items at a time and offers back and next arrows to progress or regress. Optionally, an item counter and a thumbnail sheet can be defined that will update as the carousel changes. We use the Carousel in various homepage widgets (like Just In and our Diamond Ad) and on the details pages for listing photos. Each instance of Carousel is its own unique instance allowing many of them to exist on the same page at the same time without conflict.

Another special module, which is also employed on our homepage, is the Suggester. The Suggester enhances the homepage keyword search box in our Quick Search widget to give search suggestions to the user as they type. The suggestions themselves come from an XHR call to a special endpoint in our API. The endpoint returns an array of suggestions, and the Suggester renders them as HTML. A lot of work was done to nicely handle mouse and keyboard interactions. Users can use arrow and enter keys to select a suggestions, or they can point and click with their mouse or finger. Blurring focus or hitting escape will hide the suggestions.

I gave a lot of consideration to how I wanted to handle the HTML rendering of the suggestion list. Previously, I’ve always hardcoded the relevant HTML into the JavaScript file. This time I wanted a way to define the HTML in my view file, separating it from the JavaScript that would utilize it. I decided to use two HTML templates: one for the suggestions frame and one for a suggestion item. While I’ve since discovered more robust JavaScript view techniques (hello, React!), I still enjoyed getting to play with this rather new API.

How do you test your code? Our JavaScript testing story is a bit more harrowing than our CFML one. In some respects, we didn’t need many tests, but I had learned a lesson with AIM that I should take testing more seriously. I chose QUnit because jQuery was so much a part of our codebase. The tricky thing in scaffolding the tests was to get everything to load in the right order. RequireJS, the spec, jQuery, QUnit and then the individual modules to test against. Once in place, there are very few things as psychically gratifying as a large number of tests turning green.

How do you process your JS files? We write browser-compatible JavaScript. The Gulp file will minify the JavaScript files, but we have no need of transpiling. When possible, we specify CDN versions of vendor scripts (like jQuery and various plugins), but when those links are unavailable, it falls back to locally-hosted versions in a `_backup` scripts folder. This hopefully adds up to the best JavaScript experience for our users.