Developing Feather-Weight Webservices with JavaScript

Configurable greeking

The joy and flow of creating webpage layouts can be hampered by endless cutting and pasting—or messy and temporary embedding of text in code—just to see the layout with content. Enter greeking. Exit distraction, scut work, and carpal tunnel.

greeking
n, text rendered nonsensically to demonstrate a design without the need for—or the distraction of—real content.

The greeking we are most familiar with is in Latin. Sometimes called lorem ipsum, it is a mangled version of Cicero’s De finibus bonorum et malorum and has been in use for more than 400 years.

Why greeking in JavaScript

If done locally, this problem lends itself to an approach in PHP, Python, Ruby, or especially Perl because of its facility with all-things-text. It is featherweight though and even PHP is overkill.

We want to avoid requiring programming skills to use the service. Just a line of HTML should do to get greeking for the ubiquitous elements of webpage layout: paragraphs, email addresses, URLs, titles, usernames, and proper names.

Normally JavaScript would be a sloppy choice for this. In Passing JavaScript arguments via the src attribute, however, we went over a technique for sending arguments tersely. So the typical two script combination to use local information with a remote script isn’t necessary. We just call the greeking script with the arguments in the src URL.

Live demonstration

The above is generated by calling our greeking JavaScript three times—for the title, name, and paragraphs. It can do more.

The user manual

Just put script tags in your HTML where you want the greeking to appear. The tags look like this.

<script src="http://elektrum.org/js/greeking.js?type=[type];[para_args]"
 type="text/javascript"></script>

The argument string is a regular query string; semi-colon (;) or ampersand (&–deprecated but still widely used and expected to work) joined parameter=value pairs.

Five parameter names are accepted by greeking.js. They are type, pc, wc, class, and delim.

greeking.js API

type=[para|title|name|username|email|url]
type=para. Paragraphs; the default if no type is specified. Paragraph count controlled by optional pc parameter, length of individual paragraphs controlled by optional wc parameter.
type=title. 1-8 random words in title case; no extra args.
type=name. 2-3 random words with initial caps; no extra args.
type=username. 1-3 random words joined with a dash, an underscore, nothing, or a period.
type=email. A username@ plus 2-3 random words joined on a period.
type=url. A similarly randomized host and file path.
wc=[min,max] or wc=[exact]
Word count per paragraph for type=para only. wc can be left out for random counts 6-60. A single number like wc=24 fixes the word count at 24 per paragraph.
pc=[min,max] or pc=[exact]
Paragraph count for type=para only. Like wc, pc can be left out for random counts 1-8 or given a single number for an exact count.
delim=[p|brbr|div|blockquote|br|plain]
For type=para only. p, div, and blockquote wrap paragraphs in their namesake tags.
brbr joins paragraphs on a double <br /> and br on a single. plain joins them on a double newline (“\n\n”).
class=[classFromYourCSS]
For type=para only. Eg, class=myNav will return paragraphs which start <p class="myNav">.

A simplistic forum post layout

With new found expert knowledge of the service we can tackle a simple template.

Some simple CSS

.post {
  border:1px solid #999;
  padding:1ex;
  background-color:#efefef;
}
.user, .email, .userPage {
  text-align:right;
}
.email, .text {
  font-size:small;
}
.userPage {
  font-size:xx-small;
  margin-bottom:.6em;
}
.title {
  font-size: medium;
  font-weight: bold; 
  border-bottom: 1px dotted black;
  display:inline;
}

Template snippet

greeking highlighted, template in bold

<div class="post">
<div class="user">posted by: <b><script
  src="http://elektrum.org/js/greeking.js?type=username"
 type="text/javascript"></script></b></div>
<div class="email"><script
  src="http://elektrum.org/js/greeking.js?type=email"
 type="text/javascript"></script></div>
<div class="userPage"><script
  src="http://elektrum.org/js/greeking.js?type=url"
 type="text/javascript"></script></div>
<div class="title"><script
  src="http://elektrum.org/js/greeking.js?type=title"
 type="text/javascript"></script></div>
<div class="text"><script
  src="http://elektrum.org/js/greeking.js?type=para;delim=brbr;wc=3,30;pc=2,4"
 type="text/javascript"></script></div>
</div>

Soup

posted by:

Overview

The greeking.js code does quite a bit in its 225 or so lines and the code is largely straightforward. Much of its functions are standard JavaScript that we won’t cover.

We start with a string of lorem ipsum, split it on \W+ into an array of words we can refer to with our basic building block randWord(). A more efficient approach might use the array as a circular queue, but word patterns would repeat.

String.ucFirst()

// extend String objects to do ucFirst() like Perl
String.prototype.ucFirst = function () {
  return this.substr(0,1).toUpperCase() +
    this.substr(1,this.length);
}

An interesting and less typical addition is the prototype function ucFirst() which is established immediately so all string objects in the script have access to it. More extensions to the String() object are in the Appendix: Extending the native String() class.

Extending the service

There are still a few pieces we might want to add for facility but have left out for brevity’s sake. We’d probably like to have all the block level delimiters at our disposal. Tags like <h1> can probably be omitted as using the service would involve more text than writing the actual tags but we’d certainly like <ul>, <ol>, and <dl>.

It does complicate things quickly because you’d want a recursion depth setting with the <ul> and <ol> to know if it’s just one set of <li>s or nested lists. The <dl> would auto-cascade into giving us <dt>s filled with titles and <dd>s filled with short paras.

You can easily replace the lorem ipsum. You could break out some Kanji, a natural-distribution list of words, real or imaginary, or even Greek. You could also go with randomized characters. Whatever you choose, you’d best avoid the latter. It complicates the code and may cause accidental profanity when showing layouts to clients.

The greeking.js

/*
  Code from "Developing Featherweight Web Services with JavaScript"
      http://feather.elektrum.org/
  (c)An Elektrum Press, retain this notice
      License: http://feather.elektrum.org/appendix/licenses.html
*/

// -----------------------------------
// extend String objects to do ucFirst() like Perl
String.prototype.ucFirst = function () {
  return this.substr(0,1).toUpperCase() +
    this.substr(1,this.length);
}

// -----------------------------------
// Configuration variables we only need to load once
var loremIpsum = 
 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc' +
 'feugiat. Sed suscipit libero nec sem. Vestibulum aliquet neque nec' +
 'nisl. Fusce turpis tortor, blandit in, posuere at, hendrerit vitae,' +
 'magna. Suspendisse nulla purus, dapibus vel, venenatis sed, fermentum' +
 'vitae, nulla. In suscipit, nulla sagittis cursus tempor, arcu pede' +
 'vestibulum lacus, at semper tortor ligula eget erat. Cras scelerisque' +
 'leo eu erat. Quisque egestas, turpis vel facilisis nonummy, mauris' +
 'enim rutrum quam, vitae pharetra massa quam ac felis. Vestibulum pede' +
 'quam, semper vitae, interdum vel, pulvinar ut, diam. Mauris aliquam' +
 'elementum pede. Suspendisse feugiat enim sit amet erat facilisis' +
 'sagittis. Duis laoreet turpis at risus. Sed at est et dolor tristique' +
 'scelerisque. Maecenas dui. Vivamus ut lorem. Vestibulum lacus quam,' +
 'tempus et, luctus at, scelerisque nec, libero';

var Words = loremIpsum.split(/\W+/);

// retrieve the [SCRIPT OBJ] currently being executed/called
var scripts = document.getElementsByTagName('script');
var scriptIndex = scripts.length - 1;
var myScript = scripts[scriptIndex];

var queryString = myScript.src.replace(/^[^\?]+(\?)*/,'');
var params = parseQuery( queryString );

var Delims = new Object({ p:['<p>','</p>']
                         ,div:['<div>','</div>']
                         ,blockquote:['<blockquote>','</blockquote>']
                         ,br:['<br />']
                         ,brbr:['<br /><br />']
                         ,plain:["\n\n"]
                         });

var Types = new Object({ para:1, email:1, username:1
                        ,url:1, title:1, name:1 });

var type = params['type'] || 'para';

var minP = params['pc'] ? Number(params['pc'].split(',')[0]) : 0;
var maxP = params['pc'] ? Number(params['pc'].split(',')[1]) : 0;

var minW = params['wc'] ? Number(params['wc'].split(',')[0]) : 0;
var maxW = params['wc'] ? Number(params['wc'].split(',')[1]) : 0;

var Class = params['class'];

var delim = params['delim'];
var Delimiter =  ( delim && Delims[delim] ) ? Delims[delim] : Delims['p'];
if ( Class && Delimiter[0].match(/^</) ) {
    Delimiter[0] = Delimiter[0].replace(/.$/, ' class="' + Class + '">');
}

if ( type && Types[type] ) {
  switch ( type ) {
    case 'para' : document.write( paragraphs(minP,maxP,minW,maxW) ); break;
    case 'url'  : document.write( url() ); break;
    case 'title': document.write( title() ); break;
    case 'name' : document.write( fullName() ); break;
    case 'email': document.write( email() ); break;
    case 'username': document.write( username() ); break;
  }
}
else
{
  document.write("<div style='color:#930'>Misconfigured greeking! " +
                 "Invalid or missing 'type' parameter.</div>");
}

// script ends; functions begin ------

// -----------------------------------
function randWord () {
    var email = '';
    var index = _randNum( Words.length - 1 );
    return Words[index].toLowerCase();
}

// -----------------------------------
function paragraphs ( minP, maxP, minW, maxW ) {
   if ( ! minP && ! maxP )  {
      minP = 1;
      maxP = 8;
   }
   else if ( ( ! maxP ) || ( maxP < minP ) ) {
      maxP = minP;
   }

   var paraCount = _randNum( maxP, minP );

   var paras = new Array ();

   while ( paraCount-- ) {
      if ( Delimiter.length == 2 ) {
         paras.push( Delimiter[0] + para(minW,maxW) + Delimiter[1] );
      } else {
         paras.push( para(minW,maxW) );
      }
   }
   if ( Delimiter.length == 2 ) {
      return paras.join("\n");
   } else {
      return paras.join(Delimiter[0]);
   }
}

// -----------------------------------
function para ( minW,maxW ) {
   if ( ! minW && ! maxW )  {

      minW = _randNum(20) + 10;
      maxW = _randNum(40) + minW;
   }
   else if ( ( ! maxW ) || ( maxW < minW ) ) {
      maxW = minW;
   }

   var wordCount = _randNum(minW,maxW);

   var para = new Array ();
   while ( wordCount-- ) {
      para.push( randWord() );
   }
   para[0] = para[0].ucFirst();
   return para.join(' ') + '.';
}

// -----------------------------------
function title () {
  var wordCount = _randNum(8);
  var title = new Array ();
  while ( wordCount-- ) {
    var word = randWord().ucFirst();
    if ( word.length < 5 && Math.random() < .7 ) {
      word = word.toLowerCase();
    }
    title.push( word );
  }
  title[0] = title[0].ucFirst();
  var lastIndex = title.length - 1;
  if ( lastIndex > 0 ) title[lastIndex] = title[lastIndex].ucFirst();
  return title.join(' ');
}

// -----------------------------------
function email () {
   var user = username();
   var host = server();
   return user + '@' + host;
}

// -----------------------------------
function url () {
   var host = server();
   var path = new Array ();

   var pathParts = _randNum(6) - 1;
   while ( pathParts-- ) {
      path.push( randWord() );
   }
   return 'http://' + host + '/' + path.join('/');
}

// -----------------------------------
function fullName () {
   var name = new Array ();
   var nameParts = _randNum(2,3);
   while ( nameParts-- ) name.push( randWord().ucFirst() );
   return name.join(' ');
}

// -----------------------------------
function username () {
   var joins = new Array ( '-', '.', '_', '' );
   var joinChar = joins[ _randNum( joins.length ) - 1 ];
   var nameParts = _randNum(3);
   var username = new Array ();
   while ( nameParts-- ) username.push( randWord() );
   return username.join( joinChar );
}

// -----------------------------------
function server () {
   var hostParts = _randNum(2,3);
   var host = new Array ();
   while ( hostParts-- ) {
      host.push( randWord() );
   }
   return host.join('.');
}

// -----------------------------------
function _randNum ( min,max ) {
   if ( ! min ) return false
   if ( ! max || max < min ) // 1 .. min
   {
      return Math.floor( Math.random() * min ) + 1;
   }
   else // min .. max
   {
      var range = max - min + 1;
      return Math.floor( Math.random() * range ) + min;
   }
}

// -----------------------------------
function parseQuery ( query ) {  
   if ( ! query ) return false;
   var Pairs = query.split(/[;&]/);
   var Params = new Object ();
   for ( var i = 0; i < Pairs.length; i++ ) {
      var KeyVal = Pairs[i].split('=');
      if ( ! KeyVal || KeyVal.length != 2 ) continue;
      var key = unescape( KeyVal[0] );
      var val = unescape( KeyVal[1] );
      val = val.replace(/\+/g, ' ');
      Params[key] = val;
   }
   return Params;
}

And next, presented for your enjoyment, a parade of sample usage, greeking.js in action.

« Passing JS args via src · greeking.js 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