reptile7's JavaScript blog
Sunday, August 20, 2017
 
Everything Counts Regardless of Amount
Blog Entry #378

BurnBlade's Digital Clock script is preceded by a Number of Letters script, which was authored by Greg Bland. The Number of Letters script
(a) counts the characters of an <input type="text"> box's value and then
(b) displays the count on an alert( ) box.

The Number of Letters script's numbletters.txt code page at Java Goodies is gone although you can still get the code at the corresponding JavaScript Goodies page. The aforelinked numbletters.html script page features a demo that does what it is supposed to do but also includes some strangeness that we will sort out below.

HTML interface

At the numbletters.html page the user is greeted by a theform form that holds two controls:
(1) a words text box and
(2) a push button.

<body>
<form name="theform">
<input type="text" name = "words" value = "The most letters this can do is 255" size = "40"></textarea>
<input type="button" value="Check"onclick="check( );">
</form>


• SGML-wise - per the STag, Attribute, and Eq productions in the XML 1.0 specification - the spaces flanking the = character for the <input type="text">'s name, value, and size attributes are legit but the absence of a space between the <input type="button">'s value and onclick attributes is not, although the latter doesn't cause any problems with the browsers on my computer.

The user does or does not enter some new text into the words box and then clicks the button, thereby triggering a check( ) function, which we'll get to shortly.

The "Number of Letters" description notwithstanding, the user's input can comprise alphabetic letters, numerals, symbols, and/or spaces.

The words box's initial value, The most letters this can do is 255, implies that the user can input up to 255 characters but not more than that. I don't know where the 255 number comes from. In theory, the default words maxlength is an unlimited number; in practice, inputting millions of characters may cause your browser to hang.

As you can see, there's a stray </textarea> tag up there; if you are going to enter a lot of stuff, then a textarea field would indeed be a better choice.

Check it

The check( ) function begins unproductively:

<script language="javascript">
/* ...Authorship/copyright notice... */
function check( ) {
    var l = document.theform.words.length;


The HTML DOM's HTMLInputElement interface doesn't have a length attribute, and l accordingly returns undefined; none of check( )'s subsequent operations calls on l, however.

Next we have the one part of check( ) that makes sense:

window.alert("You typed in " + document.theform.words.value.length + " letter(s).");

String objects do have a character-counting length property, and the document.theform.words.value string's length gives us the number we want, which with a bit of additional text is outputted to the user. If you don't touch the words field and click the button, you'll get the The most letters this can do is 255 length, 35; if you clear the The most letters this can do is 255 string and don't replace it with anything and click the button, you'll get 0.

The check( ) function concludes with two purposeless conditionals

if (document.theform.words.value.length != 1) {
    window.alert("Invalid number of letters, try 1 letter."); }
if (document.theform.words.value.length == 1) {
    window.alert("You learn fast!"); } }
</script>


that output messages if the user's input is not just one character and is one character, respectively. (Perhaps Greg's 'purpose' was to annoy the user, but we really shouldn't be doing that, should we?)

Keep the document.theform.words.value.length reading, chuck the rest.

wc it

With some help from our good friend Mr. Regular Expression, we can easily adapt the script to counting the words and line breaks in the input à la the Unix wc command.

Words

If we broadly define a "word" as a string of characters containing at least one vowel and no white space, then we can get the number of words in the words value via the following code:

var re_spaces = /\s+/;
var re_vowel = /[aeiouy]/i;
var word_count = 0;
var input_words = document.theform.words.value.split(re_spaces);
for (var i = 0; i < input_words.length; i++)
    if (re_vowel.test(input_words[i])) word_count++;
window.alert("Number of words: " + word_count);


The re_spaces regexp pattern matches one or more white space characters; the re_vowel regexp pattern matches a single case-insensitive ASCII vowel character. We split( ) the words value at each re_spaces separation to give an array of input_words, which are then iteratively test( )ed against the re_vowel pattern; each positive (true) test increments a word_count.

Line breaks

Getting the number of line breaks is even easier:

var re_endofline = /(\r\n|\n|\r)/g;
var linebreak_array = document.theform.words.value.match(re_endofline);
var linebreak_count = linebreak_array ? linebreak_array.length : 0;
window.alert("Number of line breaks: " + linebreak_count);


The re_endofline regexp pattern matches three types of line endings:
(1) \r\n, for the Windows platform;
(2) \n, for Unix-based platforms; and
(3) \r, for the 'classic' (pre-OS X) Macintosh platform.
The pattern's g flag enables us to match each occurrence of an ending throughout the input; we can retrieve those endings as an array via a match( ) operation.

If there are no line breaks in the input, then the linebreak_array is null, in which case the linebreak_count is set to 0.

Everything but the white space sink

The regular expression-interpreting submodule of the browser's JavaScript engine does not treat non-ASCII vowels (e.g., å, ê, ì) and symbols as word characters but wc itself certainly does, so if you want to be able to pick up a word like für (maybe you've just learned how to play "Für Elise" on the piano) and you don't mind picking up 'words' like 255 and $$$ (wc would see them as words), then you can do that with:

var re_nonspaces = /\S+/g; /* \S matches any non-white space character. */
var word_array = document.theform.words.value.match(re_nonspaces);
var word_count = word_array ? word_array.length : 0;
window.alert("Number of words: " + word_count);


Demo

Time for a demo, eh? Enter whatever you like into the textarea field below and then click the button to get the number of line breaks, words, and characters (including white space characters) in your input. A button is provided for your convenience.




Check the source of the current page for the full coding.
We'll take up the CCC sector's Money Conversion Script in the following entry.

Tuesday, August 01, 2017
 
Tic Tic Tic It Holds On
Blog Entry #377

Today's post marks our return to the Calendars, Clocks, and Calculators (CCC) sector of the Java Goodies JavaScript Repository. Continuing in reverse chronological order, the Another Great Science Calculator script is preceded by a Digital Clock script that was authored by "BurnBlade" at some point in 1997. I've lost track of how many clock scripts we've heretofore covered - "Too many," you are perhaps thinking - but because I hold that every script deserves its fifteen minutes of fame, we will give BurnBlade's script a look-over in the discussion below.

Overview/layout

The Digital Clock script's code can be viewed here and its effect is displayed at the aforelinked digitalclock.html page. The display comprises three parts:
(1) an hour:minute:second time part and
(2) an a.m. or p.m. part
that compose a 12-hour clock, and
(3) a month/date/year date part.
The script's claim to distinction is its layout, which positions the display parts in separate cells of a two-row <table>.

<form name="timedate" onsubmit="0">
<table cellspacing="0" cellpadding="0">
<tr align="center">
<td><input type="text" name="time" size="7" value="..Starti"></td>
<td><input type="text" name="parts" size="2" value="ng.."></td></tr>
<tr align="center">
<td colspan="2"><input type="text" name="date" size="8" value="........"></td></tr>
</table></form>


The use of form <input> parking spaces for the parts reflects (vide infra) the JavaScript state of the art in 1997. There are no real red flags here* but we will use a simpler structure in the demo at the end of the post.

*Admittedly, the onsubmit="0" event handler is kind of weird and doesn't serve any useful purpose, but it's not invalid, either. BTW, HTML 3.2, the current version of HTML when the script was written, qualifiedly green-lights the use of tables for layout purposes.

The display is horizontally centered by a <center> element, which is strangely (and invalidly, note the <body> markup) wrapped around all of the rest of the code:

<center><script language="javascript">
...JavaScript statements, functions, and commands...
</script>
<body onload="startwatch( );">
<form name="timedate" onsubmit="0">
...
</form></center>


Putting the center element just around the table element and its descendants is what we would have wanted to do:

<form name="timedate" onsubmit="0"><center><table cellspacing="0" cellpadding="0">...Rows and cells...</table></center></form>

Part production

The time and a.m. or p.m. parts of the display are produced by the following JavaScript:

day = new Date( );
hour = day.getHours( );
minute = day.getMinutes( );
second = day.getSeconds( );
if (hour > 12) {
    hours = hour - 12;
    part = "PM"; }
else {
    part = "AM";
    if (hour == 0) {
        hours = 12; }
    else {
        hours = hour; } }
if (minute < 10) { minutes = 0; }
else { minutes = ""; }
if (second < 10) { seconds = 0; }
else { seconds = ""; }
time = ("" + hours + ":" + minutes + "" + minute + ":" + seconds + "" + second + "");
parts = ("" + part + "");
document.timedate.time.value = time;
document.timedate.parts.value = parts;


0s are prepended to minute and second values in the 0-9 range.
• Note that the 12 noon → 1 in the afternoon hour gets an AM designation.

Can we tighten up the above code? Yes indeed:

var day = new Date( ); var hour = day.getHours( ); var minute = day.getMinutes( ); var second = day.getSeconds( );
var part = hour > 11 ? "PM" : "AM"; /* The ?: conditional operator is detailed here. */
if (hour == 0) hour = 12;
if (hour > 12) hour -= 12;
if (minute < 10) minute = "0" + minute;
if (second < 10) second = "0" + second;
var time = hour + ":" + minute + ":" + second;
document.timedate.time.value = time;
document.timedate.parts.value = part;


The date part of the display is produced by:

date = ("" + (day.getMonth( ) + 1) + "/" + day.getDate( ) + "/" + day.getYear( ) + "");
document.timedate.date.value = date;


At the demo page, the year value is 117 - a smoking gun that the script uses day's getYear( ) return versus its getFullYear( ) return therefor. Given that + operations have a left-to-right associativity, the date calculation can be recast as:

var date = day.getMonth( ) + 1 + "/" + day.getDate( ) + "/" + day.getFullYear( );

Setting the clock in motion

Loading the parent document calls a startwatch( ) function that itself calls
(a) a stopwatch( ) function, which could be used to stop the clock once it's running but doesn't do anything at this juncture (OK, it resets the watchRun flag to false), and then
(b) a dayTime( ) function that produces an initial clock reading;
dayTime( ) is re-called every 1000 milliseconds so as to update the clock.

var watchID = null;
var watchRun = false;
function stopwatch( ) {
    if (watchRun) window.clearTimeout(watchID);
    watchRun = false; }
function startwatch( ) {
    stopwatch( );
    dayTime( ); }
function dayTime( ) {
    ...Determination and display of the time, parts, and date values as detailed above...
    watchID = window.setTimeout("dayTime( );", 1000);
    watchRun = true; }
...
<body onload="startwatch( );">


If you don't care about stopping the clock (the code contains no mechanism to do so, although it is simple enough to append an onclick="stopwatch( );"-triggering button to the timedate form), then you can ditch the startwatch( ) and stopwatch( ) functions and just use a window.onload = dayTime; statement to get things under way.

Don't render me, Bro

Like many old scripts, the Digital Clock JavaScript begins and ends with some "Hiding Scripts Within Comment Tags" HTML markup:

<script language="javascript">
<!-- Begin Digital Watch
...
// End Digital Watch-->
</script>


Such hiding code has been anachronistic for quite some time although it made sense for BurnBlade to have included it back in the day. However, the <!-- Begin Digital Watch line is followed by a
document.write("<!-- Begin Hiding Script -->"); command
and the // End Digital Watch--> line is preceded by a
document.write("<!-- End Hiding Script -->"); command.

I don't quite know what to make of those document.write( ) commands. Each write( ) command writes out an intact HTML comment; the text of those comments - Begin Hiding Script and End Hiding Script - implies that the commands are also meant to serve a hiding purpose even though the commands would not effect-wise duplicate the HTML hiding code for a non-JavaScript-capable browser. Moreover, the commands strike me as somewhat nonsensical in that the write( ) method is for writing something renderable to the page whereas comments are not meant to be rendered - I'd say we should throw them out, wouldn't you?

Demo + changes

We conclude with an updated demo.



I've traded in the form and table for a <div id="clockDiv" style="text-align:center;"> and a trio of <span>s.

var clockSpans = document.getElementById("clockDiv").getElementsByTagName("span");
clockSpans[0].textContent = time;
clockSpans[1].textContent = part;
clockSpans[2].textContent = date;


Check the source of the current page for the full coding.
We'll do some letter counting in the next post via the CCC Number of Letters script.


Powered by Blogger

Actually, reptile7's JavaScript blog is powered by Café La Llave. ;-)