Refactoring: Reinventing the Wheel (in a good way)

TL;DR: After a while, in complex projects, refactoring libraries may become inevitable. Sometimes the project may require you to reinvent the wheel in your own way.

I have been working on Chroma, a long-term client project at Hipo, for more than a year. During this time, I refactored the code many times. The product's needs can change very quickly, and continuous development is a core part of the project's evolution towards a better user experience.

The most important things for a web application are UI & UX. Good UI & UX make users happy and happy users make the project team happy. From the loading animation to rich text areas, infinite scrolling to carousels, modals to input fields; there are many good UI components and techniques that are successfully used on good web applications. Luckily, there are tons of components already packaged and open sourced as libraries. For Chroma, in order to make our users happy, we have been using many of them.

Refactoring

This article is about code refactoring, specifically refactoring libraries. I'll start with an example we faced on Chroma; but first, I should talk about the elements on Chroma's home page:

  • A header bar indicating the current state (Everything in Following) which becomes a magical search bar on click
  • A photo grid with an infinite scroll
  • A fixed-position footer with shortcuts

Here is the first UX issue on the homepage: Both the header and footer bars are important, but making both of them fixed on the screen would uncomfortably narrow the middle area for scrolling through beautiful pictures.

To address this, a widely accepted practice is to hide the header while scrolling down (focusing on the photo grid) and to show it again (focusing on the search functionality) when the user scrolls up.

After little search on the internet, I found that there is a library called Headroom.js.

Headroom.js is a lightweight, high-performance JS widget that allows you to react to the user's scroll.

Headroom.js is great library, lightweight and easy to integrate. It also has an AngularJS directive which makes it perfectly fit into Chroma, since we built it with AngularJS.

At first, we quickly integrated Headroom, and Voila!

<header headroom tolerance="50"></header>

With Headroom.js, we got to show/hide the header depending on where the user scrolled, and that improved our UX. After some time, however, some other features were ready to be built, and the header started bothering us, again. The problem was that on some new pages, there was no header bar. While pages were transitioning into these pages, hiding & showing the header bar was not as seamless as we wanted. Adding and removing a class to the header is not as fast as directly adding the style values. Also, it would be better if the visibility tolerance value is little higher on up scrolling than down scrolling. Headroom did not allow us this level of customization. All of a sudden, headroom.js was insufficient for Chroma.

At that time, there were three options. We could either extend the existing library, find another library that suited us better or code the required functionality ourselves, from scratch.

For Chroma, we chose the third option. The problem was project specific, and we did not want to wait for a PR to be merged or spend time finding a suitable library. So, coding the only needed functionality from scratch seemed best.

Coding From Scratch

An angular directive is created for listening to scroll events and hides or shows the header when needed. Here are the details:

  • The scope is isolated so that directive does not pollute the parent scope, so it is reusable in any content.

  • The only parameter is tolerance object, which has up and down tolerance value fields (in pixels).

This is how it is used:

<header>  
  <h1 scroll-header  
      tolerance="grid.scrollTolerance">
    Animated Header
  </h1>
</header>  

When we look at the code we see:

  • An example controller
  • The Scroll directive
  • Two helper services (DOM Service, Scroll Service)

In the directive, we have:

  • Scroll listening init & destroy methods for directive lifecycle management
  • Window resize & scroll event listeners
  • Methods for checking & clearing the tolerance values

The DOM Service has:

  • Helper methods for getting the document height and scroll in document bounds control
  • Show & hide header functions

In the Scroll Service, we see:

  • Getter and setters for previous scroll value, current scroll value and tolerance
  • Scroll tolerance bounds control for both up & down scrolls
  • Clear scroll tolerance start point

You can have a look at the final code here.

Conclusion

Refactoring libraries can't be avoided all the time. When you have to do it, it should be done carefully considering not only the current needs but also possible future changes, so you don't end up reinventing the wheel that you just reinvented. When you refactor from scratch, you give yourself the best chance of creating something that will suit your needs for all your next versions.