These days lots of really good libraries and frameworks exist to build well structured web frontends. This is true if we start a new project on a greenfield. However using them to modernize legacy code can be quite difficult if rewriting major parts from scratch is not an option (i.e. due to time, budget or technical constraints).

In this article we will incrementally modernize a jQuery web frontend by rendering fragments of HTML with templates. This article is part of a series of blog posts in which we’ll have a look at different strategies for incrementally modernizing legacy web applications.

For this article I chose a typical piece of jQuery render code that you might find in a legacy web frontend. Most of the legacy systems that I had to deal with in recent years used jQuery, but what we describe here should work with other libraries such as Dojo or plain JavaScript as well.

Things that I often find in legacy web frontends are:

  • the DOM is created via a mixture of server side rendering and DOM manipulations
  • DOM manipulations are done by directly changing DOM elements
  • no templates are used to create HTML fragments
  • JS libraries and code snippets are used to enhance the UI – a date-picker here, a form validator there…
  • there is no clean concept of a client side router – either the page content is changed by programmatically hiding and showing elements or by browsing to another URL
  • the state is scattered throughout the application (a combination of global variables, localstorage, URL parameters and DOM elements)

The legacy web frontend:

So here is a typical piece of web frontend code with jQuery involved. It renders a table of superheroes by programmatically constructing DOM elements and then adding them to the DOM. We can see details of each superhero by clicking a link. The view is changed by programmatically hiding and showing parts of the DOM.

Here is the running web frontend: CodePen – Modernizing legacy web frontends: The Setup

"use strict";

// for demo purposes the data is embedded, usually this would be the result of an ajax fetch or similar
var heroData = [
  { name: "Superman", secretIdentity: "Clark Kent", firstAppearance: "1932", image: "https://upload.wikimedia.org/wikipedia/en/thumb/e/eb/SupermanRoss.png/250px-SupermanRoss.png"},
  { name: "Batman", secretIdentity: "Bruce Wayne", firstAppearance: "1939", image: "https://upload.wikimedia.org/wikipedia/en/thumb/7/74/Batman_Detective_Comics_Vol_2_1.png/250px-Batman_Detective_Comics_Vol_2_1.png"} //[...]
]

// bootstrap web frontend
function init(){
  renderHeroes();
}

// renders the list of superheroes
function renderHeroes() {
  var heroList = $("#heroList");
  var table = $("<table />").addClass("u-full-width");
  table.append($("<thead><tr><th>Name</th><th>Secret Identity</th><th>Details</th></tr></thead>"));
  var tableBody = $("<tbody />");
  for (var index = 0; index < heroData.length; index++){
    tableBody.append(buildHero(heroData[index], index));
  }

  table.append(tableBody);
  heroList.append(table);
};

// used by function renderHeroes to create the jQuery representation of a superhero
function buildHero(item, index) {
  var tableRow = $("<tr />");
  var nameElem = $("<td />").html(item.name);
  var secretIdentityElem = $("<td />").html(item.secretIdentity);
  var detailLink = $("<a />").attr("href", "#").html("Show Details").click({index: index}, showDetail);
  var showDetailElem = $("<td />").append(detailLink);

  tableRow.append(nameElem).append(secretIdentityElem).append(showDetailElem);
  return tableRow;
};

// renders the detail view of a hero, event contains the index of the selected hero
function showDetail(event) {
  event.preventDefault();
  var hero = heroData[event.data.index];
  var detail = $("#heroDetail");
  detail.html("");
  var image = $("<img />").attr("src", hero.image).css("width", "150px").css("float", "right");
  var title = $("<h1 />").html("Details of " + hero.name);
  var description = $("<p />").html("The secret identity of " + hero.name + " is " + 
    hero.secretIdentity + " and he first appeared in a comic in the year " + hero.firstAppearance + ".");
  var button = $("<a />").addClass("button").attr("href", "#").html("Show all heroes").click(showHeroes);
  detail.append(image);
  detail.append(title);
  detail.append(description);
  detail.append(button)
  $("#heroList").hide();
  detail.show();
};

function showHeroes(event) {
  event.preventDefault();
  $("#heroDetail").hide();
  $("#heroList").show();
};

// call the init function when the page is loaded
$( init );

This is what the corresponding markup in the HTML file looks like:

<div id="app" class="container">
    <div id="heroList" class="row"></div>
    <div id="heroDetail" class="row" style="display: none;"></div>
</div>

Adding a template:

This kind of rendering code can be quite a challenge to maintain. The business logic and data is tightly coupled with the visualization and it is hard to see and change the structure of the HTML. So lets change that by rendering the HTML fragments with templates. This is by no means anything new. With HTML templates we can decouple the data from the visualization without changing the structure. Therefore even if other parts of our web frontend rely on exactly this kind of structure we can be relatively sure that we do not run into regression problems. Also most libraries and frameworks for building web frontends use templates in one way or another. So if we later want to refactor our frontend in a more radical way this is a good starting point.

A template allows us to interpolate data properties in a piece of HTML by using special delimiters. For this example I’ll use the template function of lodash with it’s default configuration. For this kind of projects I still like to use lodash or underscore as utility libraries. This will work with any other templating library well that can compile and render String based templates into String based HTML fragments. You could even write the necessary templating logic on your own.

So lets start with the showDetail function. We could figure out the HTML structure by manually following the jQuery calls and the writing down the corresponding HTML structure. Usually in this situation I would just open the web application in a browser and then grab the corresponding HTML structure from the browsers web inspection tools. Watch out for conditional statements in the rendering logic though – in those cases we would have to take those into consideration while extracting our template. In this example we would get something like this:

<img src="https://[...]]Ross.png" style="width: 150px; float: right;">
<h1>Details of Superman</h1>
<p>The secret identity of Superman is Clark Kent and he first appeared in a comic in the year 1932.</p>
<a class="button" href="#">Show all heroes</a>

To convert this to a template we simply replace the parts that correspond to data properties with the syntax that our template engine uses – in this case ejs.

We will later pass the actual hero data to the template as an object named “hero”. So to insert the hero’s name we would use the delimiter <%= hero.name %>. For simplicity reasons we will embed the template directly in the HTML page (in production it might be a good idea to precompile the templates):

<script type="text/template" id="heroDetailTemplate">
  <img style="width: 150px; float: right;" src="<%= hero.image %>">
  <h1>Details of <%= hero.name %></h1>
  <p>The secret identity of <%= hero.name %> is <%= hero.secretIdentity %>
  and he first appeared in a comic in the year <%= hero.firstAppearance %>.</p>
  <a class="button jsShowAllHeroesButton" href="#">Show all heroes</a>
</script>

Now we are ready to modify our showDetail function: We compile our template, then we render the template with the data of the actual hero and afterwards we append the corresponding HTML fragment to the DOM. Nice and simple.

There is one catch though: The original showDetail function added a click handler to the button via jQuery. Now since our new template is basically a string we can not reproduce that functionality directly inside of our template. Instead we have to do that after rendering the template. Another option would have been to set an onclick event attribute for the <a> element.

// renders the detail view of a hero, event contains the index of the selected hero
function showDetail(event) {
  event.preventDefault();
  var hero = heroData[event.data.index];
  var detail = $("#heroDetail");
  detail.empty();

  // get the template as string (defined in html file)
  var heroDetailTemplate = $('#heroDetailTemplate').html();
  // use lodash to compile the template
  var compiledHeroDetailTemplate = _.template(heroDetailTemplate);
  // render the template with the data of a hero and attach it to the DOM
  detail.append(compiledHeroDetailTemplate({hero: hero}));
  // since the template is a plain string we have to add the click listener afterwards
  detail.find(".jsShowAllHeroesButton").click(showHeroes);

  $("#heroList").hide();
  detail.show();
};

And that’s it. We have refactored our showDetails view to use a template instead of programmatically attaching jQuery elements to each other. The rest of our web frontend, including additional visualization plugins,  should still work as expected because we did not change the HTML structure or the signature of our render function.

Our changes so far: CodePen – Modernizing legacy web frontends: Adding Templates (1/2)

Adding a template to the hero list:

The hero table is created by two functions: “renderHeroes” and “buildHero”. “renderHeroes” creates the overall structure of the hero table and then it iterates the list of heroes to create visual representations of each by using the “buildHero” function.

We could reproduce this with two templates but we could also just do the whole thing with one template and move the data iteration into the template. So lets grab the HTML representation from the browsers web inspector and then transform it into a template:

<script type="text/template" id="heroListTemplate">
  <table class="u-full-width">
    <thead>
      <tr><th>Name</th><th>Secret Identity</th><th>Details</th></tr>
    </thead>
    <tbody>
      <% _.forEach(heroData, function(hero, index) { %>
        <tr>
          <td><%= hero.name %></td>
          <td><%= hero.secretIdentity %></td>
          <td><a href="#" data-index="<%= index %>" class="jsShowHeroLink">Show Details</a></td>
        </tr>
      <% }) %>
    </tbody>
  </table>
</script>

To iterate our data structure inside of the template we use the delimiter “<% %>” that allows us to execute some arbitrary JavaScript code. I use lodash’s forEach operation to do so, but any way of iterating a list will work.

Now that we have our template we can change the “renderHeroes” function (and get rid of the “buildHero” function altogether). Again we have to pay special attention to the click handlers for the links.

In the original version those click handlers used the array index to determine which hero to show. To mimic this behavior we store the index inside of the template via a data-attribute (data-index) and then we use that to attach a click listener with the signature of the original handler.

// renders the list of superheroes
function renderHeroes() {
  var heroList = $("#heroList");
  heroList.empty();

  // get the template as string (defined in html file)
  var heroListTemplate = $('#heroListTemplate').html();
  // use lodash to compile the template
  var compiledHeroListTemplate = _.template(heroListTemplate);
  // render the template with the data of all heroes and attach it to the DOM
  heroList.append(compiledHeroListTemplate({heroData: heroData}));
  // since the template is a plain string we have to add the click listener afterwards
  // we mimic the signature of the old click handler by retrieving the index attribute
  // from a data attribute which we set in the template
  heroList.find(".jsShowHeroLink").each(
    function(){ $(this).click({index: $(this).data("index")}, showDetail); }
  );
};

We have now transformed all our rendering logic to simple templates.

If we have a lot of templates we could do a little of refactoring: We might extract the logic for getting templates, compiling them and then rendering them into an own function. We could also cache the compiled templates to improve performance further or we could precompile the templates during build time.

The final version with templates: CodePen – Modernizing legacy web frontends: Adding Templates (2/2)

Conclusion:

This article showed a generic way to add simple templates to an existing web frontend. Templates decouple the data from the visual representation and improve the maintainability and readability of the code base.

This works well if the HTML structure visualizes some static piece of data. However, since our templates are just dump strings, we have to get a bit creative when it comes to synchronization between the DOM and the underlying data and logic (i.e. user events or data updates).

If updating and data binding are used in the legacy web frontend then they are usually implemented by some custom logic. Since templates are so basic (convert a String to a String) we could easily customize our render logic to fit most of these scenarios.

In a next step we will have a look at how we can use more sophisticated mechanism to free us from managing the synchronization between DOM and logic manually. I’ll use Vue.js as an example since it is less opinionated and more lightweight than most other recent UI libraries. This makes it a nice match for gradually modernizing legacy web frontends.

…I just have to find time to write part 2 though. So stay tuned 🙂

Modernizing legacy web frontends: adding templates (part 1)
Tagged on: