Developing Feather-Weight Webservices with JavaScript

Pangrams for typographers

For our own debut webservice, let’s provide something for the typographers of the world. Dealing with different fonts in browsers can be a hassle. To know if they are being displayed properly you would have to set up lots of tiresome and redundant HTML. A perfect chance to bring a service to bear.

pangram
n, (Greek: pan gramma, “every letter”) a piece of text which uses every letter of the alphabet; pangrams are frequently used to display Roman typefaces; also holoalphabetic sentence. (source Wikipedia)

Extreme JavaScript

Snicker away, scoff and mock. Extreme programming and JavaScript likely seem mutually exclusive but XP is a philosophy, not a rule set or a FIFO stack of elitist programming languages.

An excellent online text for designing software that will match clients’ needs and expectations is Extreme Perl. One of the excellent recommendations therein is the use of stories and story cards. This same technique has long enjoyed popularity in another industry where cargo containers of money and resources need to be managed tightly: film.

A simple graphic representation, especially in the client’s pen, goes miles to communicating the things that can be elusive, or worse, debatable, when expressed verbally. It’s the 1,000 word shorthand. Everyone involved can look at it and say yes without wondering what they really just agreed to.

Our requirements list is based on typical typographical display needs. Pangrams are most often used to showcase typefaces (the correct term for what has come to be known instead as fonts). Here’s our simplistic story card, a display sample for American Typewriter Condensed.

Pangram for American Typewriter Condensed

That’s what we’ll try to emulate. With visual requirements in front of us, verbal requirements are easy to write.

Requirements

  1. Provide a pangram service via remotely called JavaScript.
  2. For variety, provide for different pangrams on request or randomly select one if none is specified.
  3. Allow the font family group to be selected by the user from standards: book, sans, ornate, mono.
  4. Allow the specific font to be overridden on request.
  5. Provide for cascading font sizes.

A bad habit of many, if not most, hackers in just about every language from JS to C++ is starting to code before you’re sure what you really want the end result to be.

The two best ways to force—did we say force? We meant gently guide—code design and creation to conform to the goals of a project, namely satisfying the user and client and keeping the phones quiet at the help desk, are writing tests and doing UI specifications before a byte of code is nibbled.

Our applications are a bit light, and difficult besides, for software QA and testing. The UI, therefore, is where we should begin. If we know the data we’re dealing with and how users expect to provide it, the battle is mostly over.

We are using the idiom. One local script to set configuration and then we’ll call the remote script. First, we check what the local configuration might look like with all the possible settings we intend to support.

While mock ups and story cards of the final version are valuable, the real trick is user interaction. How will a thing be used, and maybe the most overlooked but really obvious concern: how will a thing be misused. It will be.

UI: a sample pangram.js call

<script type="text/javascript">
<!--
 pangram = 'sphinx';
 family = 'book';
 font = 'bookman old style';
 sizes = [ 8, 10, 12, 14 ];
 showSize = 1;
 showFace = 1;
//-->
</script>
<script type="text/javascript"
  src="http://elektrum.org/js/pangram.js"></script>

With both our requirements and our interface coming together there has already been scope creep. We didn’t intend to support echoing additional information like showing the size and name with the samples but the tester sneaked it by the project manager after having a chat with the client.

For the love of a unified workplace, don’t let this happen. Have the client revise the story card and send the tester and the PM on a team building retreat.

The revised story card

Pangram for American Typewriter Condensed, revised

The supported control variables

pangram
One of a preset list: fox, sphinx, french, dutch, and abc. We named them by recognizable keywords. Supporting more is possible, of course. If no pangram is specified, we’ll select one at random.
family
One of the standard groups: book, sans, ornate, mono. We leave out Gothic, black, and a few other edge cases which are barely known, let alone supported, outside of the typographical world.
font
This is an override. It will try to put the indicated font first in the list before the family values.
sizes
An array of point sizes. We could also allow other measures supported by CSS but points are standard and the revisions at this point will cost the client extra. If sizes is omitted or misused, the pangram will write out just once at 12 points.
showSize
Boolean. If there is a true value, show the sizes like the story card.
showFace
Boolean. If true, show the list of faces we’re trying to display; it’s still up to browser whether they’re available for display.

The typeface lists are an optimistic implementation in the interest of clarity and brevity. We could use hundreds of names quite easily in search of, say, a Gothic or a black face font in an attempt to find one in every browser on every OS.

The typefaces

var Family = new Object({book:['caslon','garamond','serif']
                        ,ornate:['zapfino','fantasy','cursive']
                        ,sans:['optima','frutiger','sans-serif']
                        ,mono:['monaco','courier','mono-space'] });

Now we have access to an array of CSS face names. Family['book'].join(', '), for example, gets us: 'caslon', 'garamond', 'arrus', 'serif'.

The pangrams

var Pangram = new Object();
Pangram['fox'] = 'The quick brown fox jumps over the lazy dog.';
Pangram['abc'] = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz';
Pangram['abc123'] = 'AaBbCcDdEeFfGgHhIiJjKkLlMm,.!?0123456789';
Pangram['sphinx'] = 'Sphinx of black quartz judge my vow.';
Pangram['french'] = 'Portez ce vieux bon whiskys au juge blond qui fume.';
Pangram['dutch']  = 'Sexy qua lijf, doch bang voor het zwempak.';

A Google search for “pangram” will turn up hundreds more. You could even create originals for your own use.

Data structures are in place. Time to write the programmatic parts of the code in our server side file pangram.js. Revisit the UI.

Client call to pangram.js

<script type="text/javascript">
<!--
 pangram = 'sphinx';
 family = 'book';
 font = 'bookman old style';
 sizes = [ 8, 10, 12, 14 ];
 showSize = 1;
 showFace = 1;
//-->
</script>
<script type="text/javascript"
  src="http://elektrum.org/js/pangram.js"></script>

Before we start using the data handed off we have to assume the user will do it wrong. So we need to use the technique we looked at in Remotely called JavaScript with local configuration to get at variables through the window object. That way if they’re missing, it’s just a missing property instead of a fatal error.

We’ll just bring everything in under the same name to avoid confusion.

Filter argument variables safely

var pangram = window.pangram;
var font = window.font;
var sizes = window.sizes;
// pseudo-boolean with ternary operator
var showSize = window.showSize ? 1 : 0;
// same idea as showSize but more "correct"
var showFace = new Boolean( window.showFace );

Now that our data is safe to look at, the first interaction with the client’s call is to check what pangram, if any, was requested. We try to look it up in the Pangram object. If it returns a pangram, great, we use it. If not, we select a pangram from Pangram at random. We’ll need a function, chooseRandom(), to do it.

Which pangram will we display

var display = Pangram[pangram] || chooseRandom();

display contains the text of the pangram we’ll display. If the user failed to specify a pangram in the configuration script, or if he specified a non-existent one, chooseRandom() kicks in to select.

As an object, or an associative array, with named keys, Pangram cannot be accessed by numeric index like Pangram[1], so it’s a bad candidate for random access. We build up a simple intermediary array of the key names to make it simpler.

We could easily hard code an array matching Pangram’s keys but it would be a mistake. They might get out of sync or have accidental spelling differences, etc. Never type something twice that’s available programmatically. You’ll make mistakes. Compilers generally don’t.

chooseRandom()

function chooseRandom () {
  var Name = new Array();
  for ( var i in Pangram ) {
    Name.push(i);
  }
  var randIndex = Math.floor( Math.random() * Name.length );
  var name = Name[randIndex];
  return Pangram[name];
}

Next, check the typeface variables and select a default if no valid family was provided.

Family choice and size validation or default

var family = Family[family] || Family['book'];

if ( font ) {
  // we're writing this to the screen, must be clean
  font = font.replace(/</g, '&lt;');
  font = font.replace(/>/g, '&gt;');
  family.unshift( font );
}

if (!( sizes && sizes.constructor == Array )) {
  sizes = new Array(10,12,18,24);
}

All the logic is done. Time to work on the display. We set up some CSS information which we’ll inline with style attributes in our HTML. We want a bounding box so we can control things like the background color and text color. That way if the service ends up being used on a yellow page with pink text at, least one part of the page will look good.

We also want to define the various elements from the story card. The pangrams themselves. A headline of the fonts which are being used, or being tried; remember we are at the mercy of the browser as to whether a given font will render or not. We let the head style handle our capitalization too. It’s a bit sad that this simple functionality is native to CSS but not JavaScript (we develop a JavaScript version as a prototype of the String() object in Configurable greeking).

We need the mini point size indicators along the left. And lastly we add a little branding caption.

A little foresight is needed for these to look good. Font sizes over 30 point are likely to make a pangram wider than any given browser window. We account for that by hiding the over flow of the bound box.

Style information

var bound = 'border:1px solid black;overflow:hidden;' +
    'background-color:white;'

var p_style = 'padding:3px 3px 3px 5px;position:relative;' +
    'white-space:pre;';

var head = 'position:relative;text-transform:capitalize;' +
    'text-align:center;font-weight:bold;color:#fff;' +
    'background-color:black;font-size:14pt;padding:0 4px 0 3px;';

var pt = 'position:relative;color:#99a;float:left;' +
    'font-size:9pt;width:3em;text-align:right;clear:left;' +
    'vertical-align:text-bottom;padding-right:4px;margin-left:-1ex;';

var brand_caption = 'font-size:x-small;text-align:right;' +
    'margin-top:1px;padding:2px;font-style:italic;'

Branding

// branding stamp, feel free to change to your own site
var service_from = 'http://feather.elektrum.org/';

We’re ready to start writing output by opening the bounding box.

document.write('<div style="' + bound + '">');

We write out with the headline if showFace is true.

Show the font names upon request

if ( showFace ) {
  document.write('<div style="' + head + '">' +
                 family.join(', ') + '</div>');
}

Now write out the pangram at as many sizes as were specified or if none was, use the default array of sizes we set up.

Write out the pangrams at various sizes

for ( var i in sizes ) {
  var style = p_style + 'font-family:' + family.join(',') + ';';
  style += 'font-size:' + sizes[i] + 'pt;';
  document.write('<div style="' + style + '">');
  if ( showSize ) {
    document.write('<div style="' + pt + '">' +
                   sizes[i] + 'pt</div>');
  }
  document.write( display + "</div>\n");
}

That’s the end, close our bounding box for well-formed HTML. This is not a minor concern. If your HTML is not perfect, it might completely trash the layout of the page its imported into. Your follow up service might not be greeted warmly if you’ve generated a reputation for screwing up websites with your first.

Well-formedness counts

document.write('</div>');

Putting it all together

The client call again

<script type="text/javascript">
<!--
 pangram = 'sphinx';
 family = 'book';
 font = 'bookman old style';
 sizes = [ 8, 10, 12, 14 ];
 showSize = 1;
 showFace = 1;
//-->
</script>
<script type="text/javascript"
  src="http://elektrum.org/js/pangram.js"></script>

The pangram service

pangram.js compleat

var bound = 'border:1px solid black;overflow:hidden;' +
    'background-color:white;'

var p_style = 'padding:3px 3px 3px 5px;position:relative;' +
    'white-space:pre;';

var head = 'position:relative;text-transform:capitalize;' +
    'text-align:center;font-weight:bold;color:#fff;' +
    'background-color:black;font-size:14pt;padding:0 4px 0 3px;';

var pt = 'position:relative;color:#99a;float:left;' +
    'font-size:9pt;width:3em;text-align:right;clear:left;' +
    'vertical-align:text-bottom;padding-right:4px;margin-left:-1ex;';

var brand_caption = 'font-size:x-small;text-align:right;' +
    'margin-top:1px;padding:2px;font-style:italic;'

var Family = new Object({book:['caslon','garamond','serif']
                        ,ornate:['zapfino','fantasy','cursive']
                        ,sans:['optima','frutiger','sans-serif']
                        ,mono:['monaco','courier','mono-space'] });

var Pangram = new Object();
Pangram['fox'] = 'The quick brown fox jumps over the lazy dog.';
Pangram['abc'] = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz';
Pangram['abc123'] = 'AaBbCcDdEeFfGgHhIiJjKkLlMm,.!?0123456789';
Pangram['sphinx'] = 'Sphinx of black quartz judge my vow.';
Pangram['french'] = 'Portez ce vieux bon whiskys au juge blond qui fume.';
Pangram['dutch']  = 'Sexy qua lijf, doch bang voor het zwempak.';

// branding stamp, feel free to change to your own site
var service_from = 'http://feather.elektrum.org/';

var pangram = window.pangram;
var font = window.font;
var sizes = window.sizes;
// pseudo-boolean with ternary operator
var showSize = window.showSize ? 1 : 0;
// same idea as showSize but more "correct"
var showFace = new Boolean( window.showFace );

var display = Pangram[pangram] || chooseRandom();

document.write('<div style="' + bound + '">');

var family = Family[family] || Family['book'];

if ( font ) {
  // we're writing this to the screen, must be clean
  font = font.replace(/</g, '&lt;');
  font = font.replace(/>/g, '&gt;');
  family.unshift( font );
}

if (!( sizes && sizes.constructor == Array )) {
  sizes = new Array(10,12,18,24);
}

if ( showFace ) {
  document.write('<div style="' + head + '">' +
                 family.join(', ') + '</div>');
}

for ( var i in sizes ) {
  var style = p_style + 'font-family:' + family.join(',') + ';';
  style += 'font-size:' + sizes[i] + 'pt;';
  document.write('<div style="' + style + '">');
  if ( showSize ) {
    document.write('<div style="' + pt + '">' +
                   sizes[i] + 'pt</div>');
  }
  document.write( display + "</div>\n");
}

document.write('<div style="' + brand_caption + '">pangram service from ' +
  '<b><a href="'+ service_from + '">' + service_from + '</a></b></div>');

document.write('</div>');

function chooseRandom () {
  var Name = new Array();
  for ( var i in Pangram ) {
    Name.push(i);
  }
  var randIndex = Math.floor( Math.random() * Name.length );
  var name = Name[randIndex];
  return Pangram[name];
}

Usage

To see various ways the service can be used, turn the page to Pangrams in action. Only after we see it in use, which is the core of UAT, will we be able to evaluate the project.

« Remote JS with local configuration · Pangrams in action »
Google
 
Web Developing Featherweight Web Services with JavaScript
This is version 0.57b of this manual. It is a beta version with some gaps. We are grateful for feedback.

The code is the manual has not yet been fully tested against Internet Explorer. Bug reports are welcome.
An Elektrum Press Online