AutoGate, Angular and Responsive

An overview

A few months ago, carsales set out on one of its most ambitious projects in sometime. For us, AutoGate has been one of our underpinning products for the company as its the first point of contact for our Dealer base to upload their inventory for consumers to browse online then hopefully buy. Running two systems, one for cars (normal web) and then one for everything else (built in flex) has been an overhead which has strained the company and the team for a while. So as in all things we decided to rebuild it.

It had a few objectives:
- Combine the two AutoGates - Making ease of use a priority - Work on any device and anywhere (like in a caryard) - Fast and responsive - Multilingual

After a lot of deliberation and a few false starts we decided to use Angular.js as the front end framework, grails as the middleman which would then connect to elasticsearch and oracle.

We went with Angular for a few reasons:
- We had been using it on a small scale for some other projects for a while so we had proven it in production already - Code maintenance was lightyears easier than jQuery - The framework itself was not as opinionated as other frameworks, which helped the team fit it into its own vision - Getting started in it was quick

The little issues (such as sub urls and routing, everything in the DOM is watched) we found along the way were interesting to say the least, however the transition has been worth it.

The front end

The front end sat in an individual git repository which was then submoduled into other projects as required. This allows the same patterns to be repeated globally, ensure bug fixes related to ui issues can deployed into related projects quickly and helps with not having to start a new project from scratch when we need to bootstrap a product. Tags would then be used to mark as versions.

As an overview the front end stack is:

  • Grunt.js for build
  • Bower for dependency management
  • LessCSS for css pre compilation with AutoPrefixr
  • Jade for building out the site skeleton
  • Ripping certain patterns out of the Bootstrap project
  • Angular.js integration
  • Using KSS to generate a style guide which could then be tested via Wraith at a css pattern level to determine breaking changes
  • The methodology would be using OOCSS at a level of patterns > modules > skins.

So as we went along, the design changed a bit, but because of the set up the project would not need to change much on the whole. This allowed for us to rapidly change things as needed.

What the post is actually about

What I personally underestimated though was the responsive front end. The new grid in bootstrap 3 was streets ahead of the BS2 version, which we adopted fairly early in the pre release versions of v3 bootstrap. However it doesn't (and really cant) solve problems related to smaller resolutions.

Mobile vs Desktop Autogate listing page

The problem mainly has been the HTML. Working around responsive patterns using the same hierarchy as other displays is tedious. Its ends up being easier for developers to use a adaptive approach because the sites generate two different sets of html. At that point however, people will just build the sites out as two differing products rather than a singular one.

Of course, We didn't do this. Because #yolo right.

Mobile first can be a great thing (for marketers and buzz word enthusiasts), however it doesn't really work when you need to make a product for IE7 and up. (I also think making things for "Mobile devices first" is an overly simplistic view of a bigger problem). Usually this just relates to resolution and changing patterns and page layout based on that.

Patterns can and should change based of touch then resolution of the area of the element, not just the whole window (a failing of the current implementation of media queries, which other people see as well).

If your using making a responsive site, tying elements display into the resolution of the whole page limits you somewhat to make patterns repeatable. Things like Polymer and using the shadow DOM could fix this to some degree in the future, but for now we can fix that via angular.

Get to the point

So Angular has this thing called the Switch statement, which will let you change the layout based on a variable you pass to it. So if I pass a boolean variable there, the layout will change based on whether its true or false. Which is super handy for responsive design simply because it could change the html completely depending which area its in right? So if we made a service called resolution service.

/* Display Services */

angular.module('Display.services', [ 'ngResource' ])  
.factory('ResolutionService', ['$window', '$rootScope', function(win, rootScope) {

  // Init Object
  var displayOptions = {};

  // Resolution breakpoints
  displayOptions.tinyScreen   = 480;
  displayOptions.smallScreen  = 768;
  displayOptions.mediumScreen = 992;
  displayOptions.largeScreen  = 1200;

  // Device Pixel Density for Photos
  displayOptions.pixelRatio   = win.devicePixelRatio > 1;

  // Device Resolution (Falsy)
  displayOptions.resolutionInit = function() {
    displayOptions.tinyResolution     = win.outerWidth <=  displayOptions.tinyScreen;
    displayOptions.smallResolution    = win.outerWidth >   displayOptions.tinyScreen   && win.outerWidth <= displayOptions.smallScreen;
    displayOptions.mediumResolution   = win.outerWidth >   displayOptions.smallScreen  && win.outerWidth <= displayOptions.mediumScreen;
    displayOptions.largeResolution    = win.outerWidth >   displayOptions.mediumScreen && win.outerWidth <= displayOptions.largeScreen;
    displayOptions.massiveResolution  = win.outerWidth >   displayOptions.largeScreen;

    rootScope.$broadcast('resolutionServiceChange');
  };

  var windowChange = _.debounce(displayOptions.resolutionInit, 300);

  displayOptions.resolutionInit();
  window.onresize = windowChange;

  return displayOptions;
}]);

Note: The breakpoints in this are the same as in bootstrap, which you can inject in the less and have them pull from the same file somewhere else in the angular controller.

Injecting that into our controller and assigning that to the scope

MainController.$inject = [ '$scope', 'ResolutionService'];

function MainController(scope, resolutionService) {  
  scope.tinyResolution = resolutionService.tinyResolution;
  scope.smallResolution = resolutionService.smallResolution;
  scope.mediumResolution = resolutionService.mediumResolution;
}

we can then directly access that in the DOM. So in our layout:

<section class="area-content" ng-switch="tinyResolution">  
    <table class="table table-listing" ng-switch-when="false"></table>
    <ul class="listing-grid" ng-switch-when="true"></ul>
</section>  

Which is in our listing page, since really, the use case for listings at different resolutions requires different implementations. We can still use the same scope variables without having to saturate the DOM by producing two different layouts and just hiding one at different resolutions.

So if we use touch as well, we could change the layout at different resolutions and for it for touch. So if we assign Modernizr.touch to the scope we could then have:

<section class="area-content" ng-switch="tinyResolution">  
    <table class="table table-listing" ng-switch-when="false"></table>
    <ul class="listing-grid" ng-switch-when="true" ng-switch="touch">
        <li ng-switch-when="true"><templateLink></li>
        <li ng-switch-when="false"><templateLink></li>
    </ul>
</section>  

or

<section class="area-content" ng-switch="tinyResolution && touch">  
    <table class="table table-listing" ng-switch-when="false"></table>
    <ul class="listing-grid" ng-switch-when="true">
    </ul>
</section>  

producing what we need. This will keep the DOM clean and not referencing the scope or killing it with watches for multiple items.

Using this technique we have been able to manage AG Mobile easily while keeping it all in the same system. Even if we were to change the layout for touch devices, it won't affect the other devices / resolution breakpoints.

The element resolution problem

This doesn't solve the problem I was getting at earlier, but its a start in the right direction. So can we solve that via angular? We could put a list of breakpoints where we need to change the innards like so:

<listing></listing>  
.directive('listing', ['$timeout', function(timeout){
  // Runs during compile
  return {
    scope: {},
    restrict: 'E',
    template: '<div class="listing" ng-switch="resolution"><table ng-switch-when="false"></table><ul ng-switch-when="true"></ul></div>',
    link: function(scope, element, attrs) {
      var breakpointClass="breaking"

      // Lets use a random breakpoint for out layout
      scope.resolution = element.width() > 410;

      if (scope.resolution) {
        element.addClass(breakpointClass)
      } else {
        element.removeClass(breakpointClass)
      }

    }
  };
}])

at which point this really starts looking like Polymers and shadow dom stuff. However CSS and HTML don't support this natively, at which point we have to use classes to change the css, and js to change the html structure (like the directive above). It seems like a massive failing. The ng-switch statement actually looks like it could be a viable solution, even for native html.

/* this would be nice */
.listing (max-width: 480px) {
  .image {
    display:none;
  }
}

What to take from this

Apart from the rant nearing the end, using angular services to determine the layout of the page in certain conditions works well, but it points to a few problems that are inherit with current layout technology in browsers. Hopefully one day it will be fixed, but there are always ways around different problems and this was our solution for the moment.