1
\$\begingroup\$

I have recently carried out a test for university where I was given the following instructions:

Write a simple JavaScript application that reads paragraphs of text input by the user and creates a tabular list of all the words entered. It should also display a count of words, commas and full stops below the table.

Requirements

  • The user interface should consist of a textarea, button and results table
  • Account for words and letters only, if the paragraphs contains any numbers then inform the user only words are allowed
  • The application should accept a minimum of 5 words, a maximum of 500 words
  • Treat standalone letters such as I and a as words
  • Special characters such as ( 5 _ ‘ [ ] { } “ £ $ &!% ! : ; \ / ) should either be ignored or treated as part of the word they sit within.
  • Commas and full stops should be counted

The following is the code I provided. I was told that the should have been modularised/separated out. I believed that the code was modularised as it I attempted to follow the Single Responsibility Principle.

Can anyone provide any guidance as to how I could have improved this code?

var app = app || {};
(function() {
  "use strict";

  var result,
    arr;

  app.initialize = {
    init: function() {
      app.runApp.init();
    }
  };

  //Runs the application
  app.runApp = {
    init: function() {
      $(".calculate").click(function() {
        //Get input and remove all special characters and leading and trailing whitespace
        var input = app.specialCharacters.remove($(".text-input").val().trim()).trim();
        //Create an array from the input
        arr = input.split(" ");
        //Check there is not less than the min or more than max words and
        //that the input does not contain numbers before building table.
        if (app.minOrMax.init(arr) && !app.numberChecker.init(input)) {
          app.removeErrors.init();
          app.checker.init(arr, input);
          //Full stops and commas removed at this point
          arr = app.specialCharacters.removePunctuation(input).split(" ");
          app.createWordHolder.init(arr);
        }
        //Add invalid class if there are too many/few words
        if (!app.minOrMax.init(arr)) {
          $(".number-error").css("display", "none");
          $(".minmax-error").fadeIn(1000);
          app.errorHandler.init(arr, input);
        }
        //Add invalid class if there are numbers in the input
        if (app.numberChecker.init(input)) {
          $(".minmax-error").css("display", "none");
          $(".number-error").fadeIn(1000);
          app.errorHandler.init(arr, input);
        }
      });
    }
  };

  //Create an array to hold objects with a key containing the first 
  //letter of each word with a value of arrays containing their corresponding words 
  app.createWordHolder = {
    init: function(array) {
      result = [];
      for (var i = 0; i < array.length; i++) {
        var letter = array[i][0].toLowerCase();
        if (typeof result[letter] == 'undefined') {
          result[letter] = [];
        }
        result[letter].push(array[i]);
      }
      app.buildTable.init(result);
    }
  };

  //Used to build the table from the result array
  app.buildTable = {
    init: function(result) {
      var headers = Object.keys(result),
        maxRows = 0,
        rowsHtml = '';

      //Sort the header in alphabethical order
      headers.sort();
      app.createHeaders.init(headers);
      // Determine how many rows the table will have
      for (var i = 0; i < headers.length; i++) {
        if (result[headers[i]].length > maxRows) {
          maxRows = result[headers[i]].length;
        }
      }
      // Loop over the result array based on number or rows
      for (var i = 0; i < maxRows; i++) {
        //Create a new row on each loop
        rowsHtml += '<tr>';
        //Loop through each letter
        //If the letter has an associated word add it to 
        //the row otherwise a blank td will be added
        for (var j = 0; j < headers.length; j++) {
          rowsHtml += '<td>';
          if (i < result[headers[j]].length) {
            rowsHtml += result[headers[j]][i];
          }
          rowsHtml += '</td>';
        }
        rowsHtml += '</tr>';
      }
      $(".words-header").after(rowsHtml);
    }
  };

  //Used to build the header of the table
  app.createHeaders = {
    init: function(headers) {
      // Empty the table in case of multiple tries
      $(".words-header").parent().html('<tr class="words-header"></tr>');
      for (var i = 0; i < headers.length; i++) {
        $(".words-header").append("<th>" + headers[i].toUpperCase() + "</th>");
      }
    }
  };

  //Count the number of commas and fullstops
  app.punctuationCounter = {
    commaCounter: function(input) {
      var commaCount = (input.match(new RegExp(",", "g")) || []).length;
      $(".comma-count").text(", = " + commaCount);

    },
    fullStopCounter: function(input) {
      var fullStopCount = (input.match(new RegExp("\\.", "g")) || []).length;
      $(".fullstop-count").text(". = " + fullStopCount);
    }
  };

  //Count the number of inputted words
  app.wordCounter = {
    init: function(sentence) {
      $(".word-count").text("Words = " + sentence.length);
    }
  };

  //Check if the input contains digits
  app.numberChecker = {
    init: function(sentence) {
      return /\d/g.test(sentence);
    }
  };

  //Check min or max number of words inputted
  app.minOrMax = {
    init: function(sentence) {
      return (sentence.length < 5 || sentence.length > 500) ? false : true;
    }
  };

  app.specialCharacters = {
    //Remove special characters that are not included in a word
    remove: function(sentence) {
      return sentence.replace(/(?:^|\s+)\W+(?:\s+|$)/g, " ");
    },
    //Remove commas and full stops at end of word
    removePunctuation: function(sentence) {
      return sentence.replace(/([a-z]+)\b[.,]/ig, '$1');
    }
  };

  //Check word count, comma count and fullstop count
  app.checker = {
    init: function(arr, input) {
      app.wordCounter.init(arr);
      app.punctuationCounter.commaCounter(input);
      app.punctuationCounter.fullStopCounter(input);
    }
  };

  //Used to handle the UI following an input error
  app.errorHandler = {
    init: function(arr, input) {
      $(".textarea-holder").addClass("invalid");
      $(".words-header").parent().html('<tr class="words-header"></tr>');
      app.checker.init(arr, input)
    }
  };

  //Used to remove error messages from UI
  app.removeErrors = {
    init: function() {
      $(".minmax-error").css("display", "none");
      $(".number-error").css("display", "none");
      $(".textarea-holder").removeClass("invalid");
    }
  };

  app.docOnReady = {
    init: function() {
      app.initialize.init();
    }
  };

  $(document).ready(app.docOnReady.init);

})(jQuery);
html,
body,
div,
span,
h1,
h2,
h3,
h4,
h5,
h6,
p,
a,
img,
ol,
ul,
li {
  margin: 0;
  padding: 0;
  border: 0;
  font-size: 100%;
  font: inherit;
  vertical-align: baseline;
}
body {
  line-height: 1;
  font-size: 14px;
  background: #fff;
  color: #00528e;
  font-family: Helvetica, Arial, sans-serif;
  font-weight: 400;
  max-width: 1200px;
  margin: 0 auto;
}
ol,
ul {
  list-style: none;
}
#main {
  padding: 10px;
}
#main h1 {
  font-size: 30px;
  margin-bottom: 15px;
  font-weight: 800;
}
#main .textarea-holder {
  display: inline-block;
  margin-bottom: 25px;
}
#main .textarea-holder.invalid {
  border: 3px solid #ed0000;
}
#main .textarea-holder .text-input {
  border: 3px solid #00528e;
  padding: 5px;
  color: #00528e;
  font-family: Helvetica, Arial, sans-serif;
  font-weight: 400;
  font-size: 14px;
  line-height: 20px;
  letter-spacing: 0.5px;
}
#main .calculate {
  display: block;
  border: 1px solid #00528e;
  text-transform: uppercase;
  font-family: Helvetica, Arial, sans-serif;
  font-size: 16px;
  font-weight: 400;
  text-transform: capitalize;
  text-decoration: none;
  color: #fff;
  line-height: 20px;
  letter-spacing: 0.5px;
  max-width: 150px;
  padding: 10px 10px;
  background: #00528e;
  border-radius: 0;
  text-shadow: none;
  box-shadow: none;
  -webkit-box-shadow: none;
  font-weight: 400;
  margin-bottom: 25px;
}
#main .results-table table {
  border-collapse: collapse;
  margin-bottom: 15px;
  width: 80%;
}
@media screen and (max-width: 768px) {
  #main .results-table table {
    width: 100%;
  }
}
#main .results-table td,
#main .results-table th {
  border: 3px solid #00528e;
  padding: .2em;
  line-height: 20px;
  letter-spacing: 0.5px;
}
#main .results-table .first-td {
  display: none;
}
#main .results-table tr:nth-of-type(odd) {
  background: #eee;
}
#main .error-messages .number-error,
#main .error-messages .minmax-error {
  display: none;
  line-height: 20px;
  letter-spacing: 0.5px;
  margin-top: 25px;
  width: 40%;
}
@media screen and (max-width: 768px) {
  #main .error-messages .number-error,
  #main .error-messages .minmax-error {
    width: 100%;
  }
}
/*# sourceMappingURL=style.css.map */
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="main">
  <h1>Word Counter</h1>
  <div class="textarea-holder">
    <textarea class="text-input" name="textarea" rows="5" cols="30">The quick brown fox jumps over the lazy dog, before the duffle bag became his toy, now he’s happy.</textarea>
  </div>
  <button class="calculate">Calculate</button>
  <div class="results-table">
    <table>
      <tbody>
        <tr class="words-header">
          <td class="first-td"></td>
        </tr>
      </tbody>
    </table>
    <p class="word-count"></p>
    <p class="comma-count"></p>
    <p class="fullstop-count"></p>
  </div>
  <div class="error-messages">
    <p class="number-error">Numbers cannot be entered. Please re-enter input without numbers.</p>
    <p class="minmax-error">You have entered too few or too many words. Minimum amount of words accepted is 5, while the maximum number of words you may enter is 500. Please re-enter input with accepted amount of words.</p>
  </div>
</div>

\$\endgroup\$
  • \$\begingroup\$ @200_success is there anything bad jumping out at you with the code I have written? \$\endgroup\$ – phantom May 26 '16 at 19:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.