This page documents the basic process for developing a simple program in JavaScript, from starting with the problem statement to having a completed program.
A simple substitution cypher uses the numbers 1 - 26 to represent the letters of the alphabet, A - Z. When a message is encoded, a specific value called an "offset" is added to each letter in the message, ignoring case. Values greater than 26 are "wrapped around"; for example, if the offset is 5, the letter X would be calculated by 24 + 5 = 29 - 26 = 3 and would then be replaced in the message by the letter C. Develop a function to take a string and an offset as input, which will return the encoded message string as its output. Develop a web page that has two boxes for input, one for the message and one for the offset, and a button which produces the encoded message at the bottom of the page when it is clicked.
This program requires several steps:
This seems pretty simple. Let's find out if it is or not.
The problem statement says to develop a function which will be called by a web page. Let's do the function first, and once that works, the web page part is easy. To make it even easier, let's do the development as a program first, using the JavaScript Runner page, then turn the encoding part into a function when it's all working. That way we can "instrument" the code as we go by using temporary alert() calls to display interim values easily. Don't forget comments!!
The first thing we need to do is get some user input. We all already know how to do that, and we need to save the input to work on it later. Here's the first cut:
// Get the user input
var message = prompt( "Enter a message to encode: " );
var offset = prompt( "Enter a numeric offset: " );
alert( "Message: " + message + "\noffset: " + offset ); // instrument the code here
See how easy this is? We've already done steps one and two of our algorithm. Now we can do step three. This involves a loop to touch every character in the message. We have to find the numeric value of the letter, add the offset, and put the new letter into the output string. Wait.... WHAT output string? We'd better define one. The let's start by adding a variable declaration, and then adding the loop, which will just be an alert call as a way of instrumenting the loop to make sure we are getting the letter we think. Because the message is a string, we can use JavaScript's String.charAt and the loop index to access each character in the message in turn. Here's the code:
// Declare variables
var outputMsg = "";
var letter = "";
// Get the user input
var message = prompt( "Enter a message to encode: " );
var offset = prompt( "Enter a numeric offset: " );
alert( "Message: " + message + "\noffset: " + offset ); // instrument code here; remove later
// Encoding loop
for( var i = 0; i < message.length; i++ ) {
letter = message.charAt( i );
alert( "Character is: " + letter );
}
So now we can get user input, get the offset, and loop throught the message addressing each letter. Next we need to get the index of the letter in the alphabet.
Hmmmmmmm......
Does JavaScript have a function to do that? Characters in JavaScript are just strings that are very short (length == 1), so is there a String function that will give us the alphabet number? Just so happens that there is a function that will provide us with the Unicode value of the character at the string index. It's called "charCodeAt()" so let's try that. (In class we did a brute-force thing of defining our own array of all uppercase letters, and added another loop to find our letter in that string to get the index. This is a bit more elegant.)
// Declare variables
var outputMsg = "";
var letter = 0;
// Get the user input
var message = prompt( "Enter a message to encode: " );
var offset = prompt( "Enter a numeric offset: " );
alert( "Message: " + message + "\noffset: " + offset ); // instrument code here; remove later
// Encoding loop
for( var i = 0; i < message.length; i++ ) {
letter = message.charCodeAt( i ); // the only change!
alert( "Character is: " + letter );
}
OK, that was pretty easy. Is this really what we want, though? The problem statement said for us to ignore case and handle the letters A - Z, wrapping around if the offset ran us past 26. Using Unicode might be slick, but it may not actually fit the problem statement. For example, the code for "A" in Unicode is 65, not 1, meaning Unicode for Z is 90. So instead of wrapping at greater than 26, we have to wrap at greater than 90 and only go back to 65. This situation makes things a bit more confusing, but it's nice to have one String function call to do all that extra work for us. So let's use it!
Now that we have our code index, we need to add the offset to it, then convert that new offset to its character and put it into the output string. BTW, the charCodeAt() function returns a type Number, not a type String, so when we alert the letter we see the number value, not the character.
So let's add the offset, and then use the matching JavaScript String function to convert back to a character representation. We'll alert it when we're done, so we can see what happened.
// Declare variables
var outputMsg = "";
var letter = 0;
// Get the user input
var message = prompt( "Enter a message to encode: " );
var offset = prompt( "Enter a numeric offset: " );
alert( "Message: " + message + "\noffset: " + offset ); // instrument code here; remove later
// Encoding loop
for( var i = 0; i < message.length; i++ ) {
letter = message.charCodeAt(i); // convert to Unicode number value
alert( "Character is: " + letter );
letter += offset; // add the offset and alert the new letter
alert( "letter: " + letter + "\ncharacter: " + String.fromCharCode( letter ) );
}
Holy crap! This isn't working at all. I entered the string "A message" and an offset of 7, and instead of seeing the letter offset of 72 (65 + 7) and the character "H", the value of letter is 657! Aha. The value entered for the offset is still being treated as a string, the way we got it back from the prompt() function. When we add it to the value of letter, one of them is a string, so JavaScript converts them both to strings and concatenates them! Not what we wanted. So, we need to convert the offset value to a number using the JavaScript function "parseInt()".
We can convert the offset in one of two places — either when we get it from prompt(), or at the point we're going to use it. Let's do the first one (flip a coin....). Here's the code:
// Declare variables
var outputMsg = "";
var letter = 0;
// Get the user input
var message = prompt( "Enter a message to encode: " );
var offset = parseInt( prompt( "Enter a numeric offset: " ) ); // the only change
alert( "Message: " + message + "\noffset: " + offset ); // instrument code here; remove later
// Encoding loop
for( var i = 0; i < message.length; i++ ) {
letter = message.charCodeAt(i); // convert to Unicode number value
alert( "Character is: " + letter );
letter += offset; // add the offset and alert the new letter
alert( "letter: " + letter + "\ncharacter: " + String.fromCharCode( letter ) );
}
But now there's another problem. (sigh) If I don't type in all upper case letters in my message, some of the values don't come out as letters. Mixed case isn't being ignored as is required by our problem statement. We can fix this by making the input all upper case letters. That's easy to do in JavaScript, of course, using the function "toUpperCase()", like so:
// Declare variables
var outputMsg = "";
var letter = 0;
// Get the user input
var message = prompt( "Enter a message to encode: " ).toUpperCase(); // the only change
var offset = parseInt( prompt( "Enter a numeric offset: " ) );
alert( "Message: " + message + "\noffset: " + offset ); // instrument code here; remove later
// Encoding loop
for( var i = 0; i < message.length; i++ ) {
letter = message.charCodeAt(i); // convert to Unicode number value
alert( "Character is: " + letter );
letter += offset; // add the offset and alert the new letter
alert( "letter: " + letter + "\ncharacter: " + String.fromCharCode( letter ) );
}
Now we're getting all uppercase letters, which is good, except we are getting apostrophe letters in place of spaces, and the Z character isn't wrapping. The message "A message UVWXYZ" is encoded as "H'TLZZHNL'\]^_`a". So we're running off the end of the alphabet, since we never put in the part that wraps around. We also need to handle the spaces by leaving them alone. Finally, we need to put the encoded characters into the output string. Here's some new code to fix that stuff, and to output the result in an alert:
// Declare variables
var outputMsg = "";
var letter = 0;
// Get the user input
var message = prompt( "Enter a message to encode: " ).toUpperCase();
var offset = parseInt( prompt( "Enter a numeric offset: " ) );
alert( "Message: " + message + "\noffset: " + offset ); // instrument code here; remove later
// Encoding loop
for( var i = 0; i < message.length; i++ ) {
if( message.charAt(i) == " " ) {
outputMsg = outputMsg + " ";
continue;
} else {
letter = message.charCodeAt(i); // convert to Unicode number value
alert( "Character is: " + letter );
letter += offset;
if( letter > 90 ) {
letter -= 26; // wrap around to the beginning of the alphabet
}
alert( "letter: " + letter + "\ncharacter: " + String.fromCharCode( letter ) );
outputMsg = outputMsg + String.fromCharCode( letter );
}
}
// Output the resulting string
alert( "outputMsg: " + outputMsg );
ALL RIGHT!! Now it's working. Input the string "abc wxyz" and we get back the output "HIJ DEFG" which is correct. Now it's time to take out instrumentation and convert it to a function:
// Declare variables
var outputMsg = "";
var letter = 0;
// Get the user input
var message = prompt( "Enter a message to encode: " ).toUpperCase();
var offset = parseInt( prompt( "Enter a numeric offset: " ) );
// Encoding loop is inside the function, which takes two arguments
function encode( msg, offs ) {
for( var i = 0; i < msg.length; i++ ) {
if( msg.charAt(i) == " " ) { // handle spaces
outputMsg = outputMsg + " ";
continue;
} else {
letter = message.charCodeAt(i); // convert to Unicode number value
letter += offs; // encode by substitution
if( letter > 90 ) {
letter -= 26; // wrap to beginning of the alphabet
}
outputMsg = outputMsg + String.fromCharCode( letter );
}
}
return outputMsg;
}
// Call the function and output the resulting string
encode( message, offset );
alert( "outputMsg: " + outputMsg );
The last step is to get everything into a web page, so that it works with two input boxes and a button, and the output is displayed in an area at the bottom. This is easy to do using jsFiddle, so it is left as an exercise for you to do on your own. To finish up, then, you can either save the HTML code and the JavaScript code in two separate files as we've seen how to do already, or you can keep everything in the same file, which we've also seen how to do. Just copy the code from the jsFiddle page areas into the appropriate file or location and enjoy!
One thing that isn't tested yet is error handling. What happens if the user inputs numbers? What if she inputs special characters like the carat or dollar sign? What happens if there are Unicode code points embedded in the input? These and other questions should be addressed prior to trying to release this to the world.