JavaScript (en)coding

I’ve been thinking about programming languages lately. For years, I wrote all my little utility programs (like the address label and file folder label programs) in Perl, but Ruby seems very attractive and certainly has a lot of momentum behind it now. Unfortunately, I’m not getting much practice with Ruby because I just don’t have many tasks I need to automate right now. Rewriting my old Perl utility programs in Ruby—while it may have some pedagogical value—just seems a waste of time and I can’t work up any interest in doing it.

In the meantime, my older son, who’s 10, has gotten interested in making web pages and wants to learn JavaScript. I think JavaScript is a terrible first language to learn because you tend to spend all your time figuring out the browser interface stuff instead of learning about branches and looping and breaking your program into subprograms. Rhino would probably remove those objections, but it would also remove the fun of having your program’s work in your web browser.

And I have to admit, having the browser’s user interface available “for free” is a powerful incentive to use JavaScript. As long as the program doesn’t have to do things that are outside of JavaScript’s “sandbox” restrictions, like write files to disk, it seems like a reasonable choice.

Since my son is also learning about encryption in math—the teacher is using secret codes as the motivation for several topics—I thought I’d work up a web page that does a simple Caesar shift and use it to explain to him how the process he does with an encryption wheel can be translated to a programming language.

Here’s the page:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
   "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
   <title>Caesar Cipher</title>

   <script>

   // This function takes a string (textIn), an integer (charShift),
   // and a boolean (lettersOnly), and returns an encrypted or 
   // decrypted string using the Caesar shift. 
   function caesar(textIn, charShift, lettersOnly) {
      var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
      var textOut = "";
      var n = textIn.length;

      // We only use upper case letters.
      textIn = textIn.toUpperCase();

      // Account for a negative shift. We need to do this because
      // JavaScript's remainder operator (%) returns a negative
      // number when the first operand is negative, and that would
      // mess up our calculation of newPosition. This loop keeps
      // adding 26 to charShift until it's no longer negative. As
      // long as charShift can't be negative, newPosition can't be
      // negative.
      while (charShift < 0) {
         charShift += 26;
      }

      // Go through the input, shifting letters and composing the output.
      for (var i=0; i<n; i++) {

         // Get the next character and its position in the alphabet.
         // 'indexOf' returns -1 if the character isn't found.
         var c = textIn.charAt(i);
         var position = alphabet.indexOf(c)

         // If it's a letter...
         if (position >= 0) {

            // Calculate the shifted position. The '% 26' part
            // "circles around" to the beginning after Z.
            var newPosition = (position + charShift) % 26;

            // Add the new letter to the output string.
            textOut += alphabet.charAt(newPosition);
         }

         // If it's not a letter...
         else {
            // If we should ignore non-letters...
            if (lettersOnly) {
               // Do nothing.
            }
            // If we should include non-letters...
            else {
               // Pass the character through, unchanged.
               textOut += c;
            }
         }
      }

      // Return the output.
      return textOut;
   }

   // This function collects the information from the form elements on
   // the page, calls the encryption/decryption function ('caesar'), and
   // puts the output into its form element.
   function doCaesar() {
      var textIn = document.getElementById("in").value;
      var outField = document.getElementById("out");
      var charShift = parseInt(document.getElementById("shift").value);
      var lettersOnly = document.getElementById("letters").checked;

      // Set charShift to zero if it isn't a number.
      if (isNaN(charShift)) charShift = 0;

      outField.value = caesar(textIn, charShift, lettersOnly);
   }

   </script>

   <style type="text/css">
      textarea, input#shift {
         font-family: monospace;
         font-size: medium;
      }
      input#shift {
         text-align: right;
      }
   </style>

</head>

<body>
   <h1>Caesar Cipher</h1>
   <form onsubmit="return false;">
      <p>Input text:<br>
      <textarea id="in" cols="50" rows="8">This is a coded message.</textarea></p>
      <p>Shift:
         <input type="text" id="shift" size="5" value="13">
        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
         <input type="checkbox" id="letters" value="yes">Letters only 
         &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
         <input type="button" value="Encipher" onclick="doCaesar()"></p>
      <p>Output text:<br>
      <textarea id="out" cols="50" rows="8">GUVF VF N PBQRQ ZRFFNTR.</textarea></p>
   </form>
</body>
</html>

The script contains two functions, caesar and doCaeser, which may seem like a bit of overkill, but I wanted the function that actually does the encryption to be separate from the one that does all the communication with the form elements.

Because the audience is a ten-year-old, I’ve given the functions more lines of code and made each line of code do less that I normally would. I’ve also included many more comments than is my usual habit.. Oddly though, the longest comment, which explains the loop that turns a negative shift into an equivalent positive shift, differs little from what I’d write for myself. JavaScript’s modulus operator returns a negative value if the first operand is negative, which isn’t true for every language. For example, Perl, Python, and Ruby all say that -17 % 5 = 3, but JavaScript, C, and Fortran say -17 % 3 = -2. (Yes, I just wrote a Fortran program to check, the first one in over 25 years. Maybe closer to 30 years. It felt weird writing Fortran in a text editor instead of on a keypunch machine.)

(Image lifted from this wonderful page.)

The only other thing of note is the onsubmit="return false;" attribute in the <form> tag. It prevents the browser from “submitting” the form if you hit the Return or Enter key while typing in the shift amount. Without that attribute, an inadvertent Return or Enter will cause the page to reload, erasing whatever secret message you’ve typed into the input field.

How does this work as a teaching aid? I have no idea. Long days at work for men and other homework for my son have kept us from talking about the program. He should understand the HTML section and I’m going to try to get him to ignore the CSS section. I think I’ll have him encrypt and decrypt a short message by hand and we’ll talk about how each step he does corresponds to a line or set of lines in the program. He should understand the modulus operator, because he recently had some homework where he need to figure out the remainder after integer division. We’ll see how it goes.

Update I should have mentioned another difference in how I would have written the program if it were not aimed at a 10-year-old audience: I would have used the charCodeAt and fromCharCode to convert the characters to numbers rather than creating an alphabet variable and using indexOf and charAt. And in fact, that’s how I originally wrote the caesar function. But that’s because I’m used to the idea of ASCII (or, with JavaScript, Unicode) values for characters; subtracting off the value of “A” before the shift and adding it back on after the modulus seems more natural to me than doing searches along some artificial string. When I started fleshing out the comments, though, I realized that explaining Unicode was going to take a long time and distract from the main points. Indexing characters along the alphabet string is much more in keeping with how the kids are learning to encrypt by hand.

Tags: