Search code examples
javascriptcoffeescriptlist-comprehensionnested-loops

Refactoring Nested JavaScript Loops as CoffeeScript Comprehensions


I have the following JavaScript that I'd like to translate to CoffeeScript:

function initPage() {
  var tr = document.getElementsByTagName('tr')[0];
  labs.forEach(function(lab) {
    var td = document.createElement('td');

    // Create a header for each lab.
    var h2 = document.createElement('h2');
    h2.innerHTML = lab.name;
    td.appendChild(h2);

    // Create a div for each machine in a given lab.
    for(i = lab.first; i <= lab.last; i++) {
      var machine = ((i < 10) ? "0" : "") + i;
      var div = document.createElement('div');
      div.setAttribute('id', lab.name + "-" + machine);
      div.setAttribute('class', 'Grey');
      div.innerHTML = machine;
      td.appendChild(div);
    }

    // Append the new table data element to the table row.
    tr.appendChild(td);
  });
}

Right now my CoffeeScript translation looks something like this:

initPage = () ->
  tr = document.getElementsByTagName('tr')[0]
  labs.forEach (lab) ->
    td = document.createElement 'td'

    # Create a header for each lab.
    h2 = document.createElement 'h2'
    h2.innerHTML = lab.name
    tr.appendChild h2

    # Create a div for a machine given the machine number
    createDiv = (i) ->
      machine = if i < 10 then "0#{i}" else "#{i}"
      div = document.createElement 'div'
      div.setAttribute 'id', "#{lab.name}-#{machine}"
      div.setAttribute 'class', 'Grey'
      div.innerHTML = machine
      td.appendChild div

    # Create a div for each machine in a given lab
    createDiv machine for machine in [lab.first..lab.last]

    # Append the new table data element to the table row.
    tr.appendChild td

Is there a better, more idiomatic way to create the divs for each lab? Would it be better to avoid the createDiv function and do something like:

for i in [lab.first..lab.last]
  machine = if i < 10 then "0#{i}" else "#{i}"
  div = document.createElement 'div'
  div.setAttribute 'id', "#{lab.name}-#{machine}"
  div.setAttribute 'class', 'Grey'
  div.innerHTML = machine
  td.appendChild div

The CoffeeScript language reference says

Most of the loops you'll write in CoffeeScript will be comprehensions over arrays, objects, and ranges.

and

Comprehensions should be able to handle most places where you otherwise would use a loop, each/forEach, map, or select/filter

I'm new to the idea of list comprehensions and want to make sure that I'm translating this code in a way that leverages the strengths of CoffeeScript appropriately.


Solution

  • Would it be better to avoid the createDiv function and just inline it?

    Yes, that function looks a bit superfluous.

    I'm new to the idea of list comprehensions and want to make sure that I'm translating this code in an appropriate way

    The aim of list comprehensions is to build new lists, like map and filter would do it. And for which loops or (inappropriately) forEach was used often, manually pushing to an array.

    However, your aim is not to create an array, but a DOM element only. Comprehensions don't help here, you will need to use them as loops to execute side effects.