Solving simple problems with client-side web applications

Whether we like it or not, JavaScript has been exponentially growing in popularity. While the benefits can be obvious, there as some side-effects that are usually overlooked. As more and more features are being pushed to modern web browsers (making it compelling to build web applications that work in almost every device), on the other hand feature fragmentation is getting worst every day. We can point fingers at all the different browser vendors and their constant push of new updates. To make matters worse, the same browser might have different versions for Desktop and Mobile devices (Chrome != Chrome for Android != Android Browser). Some of this problems could be avoided if everyone always ran the latest versions (and Google Chrome started off with great ideas to make that possible) but the reality is very different. Take a look at can I use to have a rough idea of feature reach and disparity. At a higher level, consider how many people still use Windows 7 (and uses IE)... Does everyone upgrade to the latest iPhone or Samsung Galaxy as soon as they come out? Most Android phones aren't even compatible with the latest Android version. If fragmentation is gigantic in the most used mobile OS, imagine how big that is problem if you account for all available web browsers. To be fair fragmentation is an issue if your audience uses a diverse variety of browsers and you are using brand new browser features. Both problems are easily identifiable, fixing them might not be so easy. Anyway, just be aware of this issues when you're working on web applications. Now that we are done the warnings, let's jump into a practical example.

Imagine a family member just asked your help to solve simple problem. Analyze each row of a given csv file (comma-separated values. Looks like a spreadsheet separated by commas and line breaks, very simple way to structure data), to verify if the element on the last column (let's call it the 'pivot'): is smaller than all elements in the first six columns and (if previous condition is meet) add a new column at the end of the row, containing the difference between the 'pivot' and the value presented on the sixth column. Quite simple, right? Anybody could do it by hand, but we are developers so let's make the machine do the hard work for us.

For this particular case we could develop a Desktop or Mobile application, but let's try to create a Web Application. Plus this problem is so simple, that server processing won't be required at all, let's do anything in client-side JavaScript. In other words, our friend's web browser will do all the work, we just provide the set of instructions it needs to run.

This problem is quite simple to solve, even so let's break it down to a list of straight forward requirements:

  • the user needs to upload a csv
  • instead of a file upload dialog, drag-drop is preferred
  • figure out a way to read the contents of the file provided by the user
  • analyze the csv file
  • return the resulting csv file to the user (user must download it)
  • it must run on the latest Google Chrome (for Desktop), don't worry about any other browser
  • all code must run on the client browser (no file uploads to our web server)

Due to the simplicity of this problem, let's jump straight into code. Just check the comments for more details. Let's start with the HTML:

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="description" content="Simple Match prem analysis">
<meta name="author" content="John Louros">
<title>Basic csv analysis</title>

<style type="text/css">
  body { font: normal 16px/20px "Helvetica Neue", Helvetica, sans-serif; background: rgb(237, 237, 236); margin: 0; margin-top: 40px; padding: 0; }
  section, header, footer { display: block; }
  header, article > * { margin: 20px; }
  h1 { padding-top: 10px; }
  [contenteditable]:hover:not(:focus) { outline: 1px dotted #ccc; }

  #wrapper { width: 600px; margin: 0 auto; background: #fff url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/7QBAUGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAACQcAVoAAxslRxwCAAACAAIcAkYAEFBpeGVsbWF0b3IgMS40LjH/4QCpRXhpZgAATU0AKgAAAAgABgESAAMAAAABAAEAAAEaAAUAAAABAAAAVgEbAAUAAAABAAAAXgEoAAMAAAABAAIAAAExAAIAAAARAAAAZodpAAQAAAABAAAAdwAAAAAAAABIAAAAAQAAAEgAAAABUGl4ZWxtYXRvciAxLjQuMQAAA6ABAAMAAAAB//8AAKACAAQAAAABAAACV6ADAAQAAAABAAAA0QAAAAD/2wBDAAICAgICAQICAgICAgIDAwYEAwMDAwcFBQQGCAcICAgHCAgJCg0LCQkMCggICw8LDA0ODg4OCQsQEQ8OEQ0ODg7/2wBDAQICAgMDAwYEBAYOCQgJDg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg7/wAARCADRAlcDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooqOgCSiiigApNw96ZRQBJRSbh70bh70ALRUdP8A+BUALRRRQAUUVHQBJSbh70bh70ygCSio6KAJKKj+SpKACik3D3paACiiigAoqOigCSik3D3o3D3oAWk3D3paKAE3D3pajooAkopNw96WgAopP+A0tABRRRQAUVHRQBJRUdFAElFFFABRRSbh70ALRRRQAUUUUAFFFFABRUe/3ooAkoqOigCSiik3D3oAWim/NTqACik3D3paACik3D3paAE3D3paTaPemUASUVHRQBJRUdG/3oAkopNw96T5aAHUUVHQBJRUdFABRTN30o3v7/nQA+imbvpSUASUb/eo6buoAm3+9G/3pm76UbvpQA+iq9O3UATUVDup+76UAP3PRv8AembvpRu+lAD6KjooAkoqOigCSiq9SUASUVHRQBJv96N/vUdFAC7vpT9/vUdN3UATb/ejf71XooAsUVXqXd9KAH0Uzd9KSgCTf70VHRQBJRv96h3U/d9KAEqSmbvpSUASUVDup1AEm/3oqOm7qAH7vpRu+lM3U2gCxRUdFAElG/3qOigCTf70VHRQBJRv96jpd30oAfn/AG6KZu+lG76UAPo3+9M3fSkoAkopm76UbvpQA/f70b/eq9SUASb/AHo3+9R0UASb/ejf71HRQAu76U/f71HRQBJv96N/vUdFAEm/3o3+9M3fSkoAk3+9FR0UASUVHRQBJv8Aeio6KAG7qZu+tM3+9G/3oAkoqOjf70ASUm760zf70UASUVHv96N/vQBJSbvrRu+tM3+9AElFR0UATbqN1Q0UATbqN1Q0UATbqZu+tMooAm3U2o6N/vQBJSbvrTN/vRv96AH7vrS1Hv8AeigCSjn+5UdHyUAP3fWl3r/k1Hv96N/vQBJRUdFAElJu+tM3+9FAD931pajo3+9AElFR7ko3+9BmSUVHRQaElFJu+tM3+9AElFR7/ejf70ASUVHRv96AJKKj3+9G/wB6AH7vrRu+tMooAm3Ubqh3+9G/3oAm3Uzd9aZv96KAH7vrRu+tMooAkoqOjf70ATbq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') repeat-x center bottom; border-radius: 10px; border-top: 1px solid #fff; padding-bottom: 76px; }
  #holder { border: 10px dashed #ccc; width: 300px; min-height: 300px; margin: 20px auto; }
  #holder.hover { border: 10px dashed #0c0; }
  #saveOutputFileBtn { margin: 0 auto; display: block; }
  .errorMessage { color: #c00 }
  .fail { background: #c00; padding: 2px; color: #fff; }
  .hidden { display: none !important; }
</style>


<script src="http://code.jquery.com/jquery-2.2.4.min.js"></script>
<!-- source code for jQuery CSV can be found here https://github.com/evanplaice/jquery-csv -->
<script src="https://cdn.rawgit.com/evanplaice/jquery-csv/c99aa27290e103cc4cf76136c86f60d4985dd0d6/src/jquery.csv.min.js"></script>
<script src="js/main.js"></script>
</head>
<body>

<section id="wrapper">
  <header>
  <h1>Drag and drop, automatic upload</h1>
  </header>
  <article>
  <div id="holder"></div>

  <p id="filereader">
    <span class="errorMessage">File API &amp; FileReader API not supported</span>
  </p>

  <p id="statusMessage"></p>    
  </article>
  <article>
  <button id="saveOutputFileBtn">save result</button>
  </article>
</section>

</body>
</html> 

Finally the JavaScript:

$(document).ready(function () {

  var processedData;
  var outputFileName;

  // initial state (right after the page loads)
  $("#statusMessage").text("Drag an csv file from your desktop on to the drop zone above to kick-off the analysis.");
  $("#saveOutputFileBtn").attr("disabled", "disabled");

  // check if this browser supports 'FileReader'. Required to read csv contents
  if((typeof FileReader != 'undefined') === false) {
    $("#filereader").addClass("fail");
  } else {
    $("#filereader").addClass("hidden");
  }

  // check if this browser support drag & drop and wire it up
  if('draggable' in document.createElement('span')) {
    var holder = document.getElementById('holder');
    holder.ondragover = function () { this.className = 'hover'; return false; };
    holder.ondragend = function () { this.className = ''; return false; };
    holder.ondrop = function (e) {
      this.className = '';
      e.preventDefault();
      readfiles(e.dataTransfer.files);
    }
  }

  // read uploaded files
  function readfiles(files) {

    if (files.length === 0) {
      return;
    }

    // set output file name
    outputFileName = files[0].name.replace(".", "_result.");

    var reader = new FileReader();

    reader.onloadend = function (evt) {
      if (evt.target.readyState == FileReader.DONE) { // DONE == 2
        var csvData = evt.target.result;

        try {
          runCsvAnalysis(csvData);
        } catch(err) {
          $("#statusMessage").text("Unsupported file or unexcepted error processing input. Try another file.");  
        }
      }
    };

    reader.readAsText(files[0]);

    $("#saveOutputFileBtn").text("save result '" + outputFileName + "'");
    $("#statusMessage").text("File upload successful!");
  }

  function runCsvAnalysis(csvContent) {
    // parse csv content to array 
    var dataArr = $.csv.toArrays(csvContent);

    // create a copy of the array
    var output = dataArr.slice();

    // check if the first row container header or values
    var firstRow = isNaN(parseInt(dataArr[0][0])) ? 1 : 0;

    if (firstRow > 0) {
      // add new column name
      output[0][12] = "Result";

    }

    // crappy algorithm, since it does not run any validation and 
    // expects the values in a particular order. For demo proposes only.
    for (firstRow, len = dataArr.length; idx < len; idx++) {
      var isMatch = false;
      var result = " ";
      var row = dataArr[idx];
      var pivot = parseInt(row[11]);

      if (pivot < parseInt(row[0])
        && pivot < parseInt(row[1])
        && pivot < parseInt(row[2])
        && pivot < parseInt(row[3])
        && pivot < parseInt(row[4])
        && pivot < parseInt(row[5])
      ) {
        isMatch = true;
        result = row[5] - pivot;
      }

      output[idx][12] = result;
    }

    // save output to processedData
    processedData = output;

    $("#statusMessage").text("Analysis complete, press the save button below to download the result.");
    $("#saveOutputFileBtn").removeAttr("disabled");
  }

  $("#saveOutputFileBtn").bind("click", function () {
    // prepare results to be downloaded
    var csvContent = "data:text/csv;charset=utf-8,";
    processedData.forEach(function (infoArray, index) {

      dataString = infoArray.join(",");
      csvContent += index < processedData.length ? dataString + "\n" : dataString;

    });

    // create a anchor element to automatically download the results file
    var encodedUri = encodeURI(csvContent);
    var link = document.createElement("a");
    link.setAttribute("href", encodedUri);
    link.setAttribute("download", outputFileName);

    link.click(); // This will trigger file download
  });

}); 

To exercise the code use the following test csv:

Column A,Column B,Column C,Column D,Column E,Column F,Column G,Column H,Column I,Column J,Column K,Column L
49,66,92,86,80,56,100,48,65,46,58,55
96,75,35,93,92,21,35,81,80,30,66,43
92,75,79,54,97,9,85,87,41,75,55,82
10,48,80,45,32,34,72,15,97,0,44,23
8,67,14,34,85,16,33,73,22,64,79,46
67,70,89,66,91,65,33,96,59,61,64,1
25,92,27,6,30,78,33,10,89,91,10,92
42,63,44,53,59,33,61,41,82,66,100,79
45,49,23,24,74,94,38,53,6,31,76,17 

Quite simple right? Keep in mind that this code was only tested in Chrome. This is a very basic example but you get the idea. Hopefully this will serve as inspiration for someone. Please send any questions or comments that you might have.

web services