I’ve just finished a very enjoyable year working at O2. I’ll describe here some of my work during that period.
The contract
The employment structure on this occasion was fairly complex: I was found by Experis while helping to create the new global Land Rover website (this project was just coming to an end).
My own limited company was contracted to Experis, they in turn were contracted to global IT services company Cognizant, and the end client was Telefónica, the brand being O2.
I was employed on a six month contract which was renewed for a further six months.
I’m also happy to enter into contracts with end clients directly through my limited company.
Technical environment
Cognizant uses Agile development, which I was familiar with from my work on the Land Rover account.
See North for a comprehensive guide to the Agile methodology.
We worked in a TDD/BDD and continuous integration environment where Jenkins was used on the back-end, integrated with Git version control. The build pipeline was well organised with functional tests and end-to-end tests for the Java via Selenium tests. MongoDB was used as a database, Tomcat as server. We used many other technologies, including Node.js and Velocity – and on the front end jQuery, Underscore and AngularJS.
I used Sublime Text as my editor first of all, along with many plugins, but later switched to WebStorm. This is a little slower, since ST is essentially just a very powerful text editor, whereas WebStorm is a fully-fledged IDE: it features built-in version control, terminal and refactoring, among other things.
For Git, I used both the command line (with many aliases) and free app SourceTree, until I moved to WebStorm.
The new O2 checkout
The first agile team I joined was a large one, assembled with the purpose of building the new O2 checkout. Objectives were to refresh the look and feel, make the checkout fully responsive (good on any device), and – crucially – to reduce the user’s journey only four steps. All by Christmas.
We worked in bi-weekly sprints, using Jira for project management, and we had all the expected Agile team members, from BAs through to QAs. There were a lot of Java devs and variable amounts of front end devs (sometimes four of us, sometimes only me). Once I had familiarised myself with Velocity syntax, one of my first responsibilities was to build a number of components for the checkout interface (“modules”), using of course JavaScript, HTML and CSS (actually Sass). Here’s a snippet from the thousands of lines of Sass I wrote, this using BEM syntax:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
.eligibility__recycle, .eligibility__perks { margin: 25px 0; position: relative; overflow: visible; padding-left: 60px; &:before { content: ''; width: 46px; height: 46px; display: block; position: absolute; left: 0; @include media($breakpoint2) { top: -4px; } } } .eligibility__recycle:before { background: url("../img/ico_o2-recycle.png"); } .eligibility__perks:before { background: url("../img/ico_o2-priority.png"); } |
I actually prefer to use indented Sass syntax (so that’s .sass not .scss), but this project was already up and running in .sass.
We used jQuery and I decided that a good way for me to make these components would be to use jQuery’s Widget Factory. This provides a very organised way to make plugins which – and this is the really useful part – can retain their state. To achieve this I included only the necessary parts of the jQueryUI codebase, keeping the JS lightweight.
I created eight different user interface components using this pattern, which all now feature on the live site. These range from a Responsive Table to an Accordion Menu which allows nested menus. Here is that Accordion Menu:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
// Author: @gavinorland $.widget('o2.accordion', { _create: function() { this.$headings = $('h3', this.element).on('click.accordion', {that: this}, this._onHeadingClick); }, _onHeadingClick: function(event) { event.preventDefault(); var that = event.data.that, $li = $(this).parent(), $heading = $('h3', $li); if($heading.hasClass('on')) { $heading.removeClass('on').next().transition({height:0}, app.easing.speeds.slow, app.easing.defaultType); } else { $('h3.on', that.element).removeClass('on').next().transition({height:0}, app.easing.speeds.slow, app.easing.speeds.defaultType); $heading.addClass('on'); var $targetContent = $heading.next(), targetHeight = $targetContent.css('height', 'auto').height(); $targetContent.css('height', 0); $targetContent.transition({height: targetHeight}, app.easing.speeds.slow, app.easing.defaultType, function() { app.scrollToElement($heading); }); } }, destroy: function() { this.$headings.off(); $.Widget.prototype.destroy.call(this); } }); |
I like the challenge of creating things like this and trying to do it in the least code possible. We supported IE8 with the entire development, so these had to be compatible with that browser (I used Oracle’s Virtual Box software for testing on my Mac). Once coded as static, they were brought to life with live data by my Java colleagues.
I became a lot more competent with Git during this project, getting to grips with interactive rebasing, the reflog and many other features of this powerful VCS. I also spent some time rewriting and streamlining the Gruntfile for the project, installing many tasks using NPM for Node.js and configuring them optimally.
I wrote mixins and placeholders and many lines of Sass, grouping these into distinct partial files for clear organisation. These were then imported Sass-side for speed and ultimately uglified and minified into CSS (all done by Grunt and Compass). I also worked with the jQuery validation plugin and with LivePerson and SessionCam configuration.
A few thousand lines of code more from everyone, much QA-ing and a few late nights, and eventually the new O2 checkout went live – on time, and to a great reception. We then went through several more successful releases and received reports of how conversion rates for O2 had greatly improved because of our work.
I worked with a lot of skilled people on this, there was a good atmosphere and it was great to have not one, but multiple successful launches.
Some refinement
Once our objectives had been met with the checkout, our large team divided into smaller Agile teams, with several working on the new responsive shop pages. I had a chance to familiarise myself with the Gulp task runner – and now prefer it to Grunt.
At this point we also refined the Gruntfiles further and stopped using the slow Ruby version of Compass and the Ruby Sass compiler, switching to Libsass, which is much faster.
Next we had a look at the grid system which had been used on some Shop pages, which was Neat, and began to carefully swap it out, working in a Git branch, in favour of Bootstrap‘s grid system. I like Bootstrap a lot.
The Upgrade Options page
We then began work on a rewrite of the page which customers would visit if they wish to upgrade their account – choosing a new device and/or a new tariff. This too needed to be totally responsive and in-keeping with other new elements of the site and it needed to integrate parts of the shop pages, yet also elements from the checkout pages.
With its various user-configurable options and the many Ajax requests used to retrieve data from the server, this page was an ideal candidate for the use of AngularJS. However, we needed to support the user pressing their Back button returning to the page and it remembering their chosen configuration (menu selections etc.). SPAs are sometimes criticised for lacking this support, although AngularJS does have built in routing for views. We needed something more powerful than this, though, so turned to the ui-router framework, which can rebuild the entire state of an SPA from its URL.
I was responsible for building device search functionality for the Upgrade Options page, using AngularJS – here’s some of the code I wrote:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
var app = angular.module('shopApp'); app.controller('SearchCtrl', [ '$rootScope', '$scope', function ($rootScope, $scope) { 'use strict'; $scope.tracking = false; $scope.currentIndex = 0; $scope.checkSearchLength = function () { if ($('#search-input').val().length > 1) { $scope.showSearchResults(); } else { $scope.hideSearchResults(); } }; $scope.showSearchResults = function () { $('.search-results').show().find('span').scrollTop(0); if ($scope.tracking === false) { $('.tiles').on('keydown', $scope.handleKeydown); $(document).on('click', $scope.handleClick); $scope.tracking = true; } }; $scope.handleClick = function(event) { if (!$(event.target).closest('#search-input').length) { $scope.hideSearchResults(); } }; $scope.hideSearchResults = function () { $('.search-results').hide(); if ($scope.tracking === true) { $('.tiles').off('keydown', $scope.handleKeydown); $(document).off('click', $scope.handleClick); $scope.currentIndex = 0; $scope.tracking = false; } }; $scope.isCurrentItem = function (el) { return el.$index === $scope.currentIndex; }; $scope.handleKeydown = function (event) { var $container = $('.search-results span'), containerHeight = $container.height(), $items = $('a', $container), totalItems = $items.length; if (event.keyCode === 40) { $scope.currentIndex++; } else if (event.keyCode === 38) { $scope.currentIndex--; } else if (event.keyCode === 27) { $scope.hideSearchResults(); return; } if($scope.currentIndex < 0) { $scope.currentIndex = totalItems-1; } else if($scope.currentIndex === totalItems) { $scope.currentIndex = 0; } $container.scrollTop(0); var $currentItem = $items.eq($scope.currentIndex), itemTop = $currentItem.position().top, itemHeight = $currentItem.outerHeight(); if(itemTop + itemHeight > containerHeight){ $container.scrollTop(itemTop - containerHeight + itemHeight); } if(event.keyCode === 13) { $currentItem[0].click(); } $scope.$apply(); }; } ]); |
I had been wanting to use AngularJS for more than a year so it was great to get going with it on this project. I am keen to use it more in future but am also aware that version 2 is going to be very different.
Memorable moments
I met a lot of good people and made a lot of friends at O2, on both the back end and the front end in Cognizant and Equal Experts, and indeed among O2’s own staff. The premises are remarkable, with a good canteen, a Costa coffee, even an on-site WHSmith – and let me not forget the foosball table (that’s right, foosball with an “s”). I think I was the second worst player, thus did not enter the Championship Tournament. Still ready to attempt those trick shots if there’s a table at my next place!
What’s next?
I’m in Florida at the moment, using the valuable time to read up further on AngularJS, React, Node.js and MongoDB – pretty much the MEAN JavaScript stack.
I should be available for another contract – ideally in the Berkshire or Surrey areas, possibly in London – some time in August. I have wide experience so will be looking for Senior Front End, possibly Front End Lead or a little more managerial. Looks like exciting times lie ahead for the web, as usual, and I’d like continue to be part of them!
Discover more from Gavin Orland
Subscribe to get the latest posts sent to your email.
Comment: