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
by
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 optionalpc
parameter, length of individual paragraphs controlled by optionalwc
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
. Ausername
@ 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 likewc=24
fixes the word count at 24 per paragraph. pc=[min,max]
or pc=[exact]- Paragraph count for
type=para
only. Likewc
,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
, andblockquote
wrap paragraphs in their namesake tags. -
brbr
joins paragraphs on a double <br /> andbr
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
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.