Weeks 9-10

The page I have been working on is just a single page with some tabs, but like all Django apps it has models, views and URLs (all Python), HTML templates, and some CSS to make it look pretty. It also has some JavaScript to control switching between tabs and give interactivity to the content within each tab. This post is about refactoring some of that JavaScript.

Over the weeks I have added two new tabs to the signature page. The functions to load each tab were values of an object, like this:

var tabFunctions = {};

tabFunctions.coolTab = function () {
    // Code for loading this tab
};

tabFunctions.anotherCoolTab = function () {
    // Code for loading this tab
};

When the user clicks on the tab named “coolTab”, tabFunctions.coolTab gets called, and to add a new tab, you add a new function to tabFunctions. (Plus some additional Python, HTML and CSS, but let’s ignore that.) The advantage of doing it this way is that it’s flexible, but the downside is that quite a lot of code is repeated between the different functions in tabFunctions. After I added the first tab, I decided to refactor the code to make it easier to add the second tab.

The tabs share a lot of things in common but also differ in some ways, so it made the most sense to create a generic Tab object, which could be extended by coolTab, anotherCoolTab, etc. Each tab consists of a panel that has a heading, a div for controls (so that the user can control the content of the tab) and a div for the content:

<section class="panel">
    <header>
        <h2></h2>
    </header>
    <div class="body">
        <div class="controls"></div>
        <div class="content"></div>
    </div>
</section>

And each tab needs to be able to:

  • Load its controls
  • Load its content initially
  • Load more specific content as specified by the user
  • Show itself (when it is clicked on)
  • Hide itself (when another tab is clicked on)

Loading controls and loading content should be kept separate because when new content is loaded in response to the user setting the controls, you don’t want to reload the controls too. So far the generic tab looks something like this:

var Tab = function (heading) {
    this.heading = heading;
    this.alreadyLoaded = false;
    // Make all the elements and append them together
};

Tab.prototype.loadControls = function () {
    // Make the controls (e.g. a select with some options)
    // Bind an event to call loadContent (e.g. when the user chooses a new option)
};

Tab.prototype.loadContent = function () {
    // Load the content, with either default or user-specified parameters
};

Tab.prototype.showTab = function () {
    if (!this.alreadyLoaded) {
        this.loadControls();
        this.loadContent();
        this.alreadyLoaded = true;
    }
    // Then show the tab
};

Tab.prototype.hideTab = function () {
    // Hide the tab
};

To make a specific tab, a new object can be defined that inherits the prototype of the generic Tab object and adjusts the functions loadControls and loadContent. However, it is in loadContent where a lot of the code is repeated, so doing it this way would still result in a lot of unnecessary work. In fact loadContent essentially does three things that can differ slightly between different tabs:

  • Gets parameters (default for the initial load, user-specified for subsequent loads)
  • Builds a URL, which encodes these parameters and encodes which view this tab talks to
  • Makes an AJAX request based on this URL. This is the same for every tab, except for the function that is called if the request is successful

So loadContent should be adjusted and three new methods added:

Tab.prototype.getParameters = function () {
    // Get and return the parameters
};

Tab.prototype.buildURL = function (params) {
    // Build and return the URL
};

Tab.prototype.onAjaxSuccess = function () {
    // Add the new content to the tab
};

Tab.prototype.loadContent = function () {
    var params = this.getParameters();
    var url = this.buildURL(params);
    // Make AJAX request that uses url and calls this.onAjaxSuccess if successful
};

The three new methods should do enough to load a basic tab, but can be altered if a specific tab needs specific behaviour. With this structure, a tab that generates its URL in a slightly unusual way can simply alter buildURL, rather than needing a whole new loadContent function, for example.

One final layer of complexity was that some tabs behaved differently, for example some loaded graphs while others loaded tables and some had inner panels while others did not. In general this was an exercise in finding the right balance between keeping the generic tab simple but repeating code for the specific tabs, and having configurations and lots of “if” statements complicating the generic tab but keeping the specific tabs simple.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s