reptile7's JavaScript blog
Monday, August 30, 2010
 
Out on the Scrolls
Blog Entry #189

In today's post, we will go through HTML Goodies' "So, You Want A Scrolling JavaScript, Huh?" tutorial and its scrolling text script.

"Not another scrolling text script! Can't we just use the marquee element? Please? Huh? Please??"

No. And I'll send you to the principal's office if you ask that again. Now, where were we? "So, You Want A Scrolling JavaScript, Huh?" presents the second of two scrolling text scripts offered by HTML Goodies' Beyond HTML : JavaScript sector, the first being that presented by the sector's "So, You Want A JavaScript Ticker Tape, Huh?" tutorial, which we recently discussed in Blog Entries #186 and #187. Design-wise, the "Scrolling JavaScript" script is closer to the "JavaScript Ticker Tape" script than to the scrolling text script of HTML Goodies' JavaScript Script Tips #35-37, which we checked over in Blog Entry #65, in that it
(a) uses a secondary index to delimit the space and text zones of its scroll, and
(b) does not append space characters to the text zone as the text zone is whittled down to its last character.

The "Scrolling JavaScript" script does sport a unique feature, however; instead of working with a preset space zone (cf. the tt_space variable in the "JavaScript Ticker Tape" script), the script creates a decrementing-length space zone on the fly via a for loop.

The "Scrolling JavaScript" script is posted at a java.txt page; be sure to unescape the &lt; and &gt; character entity references that bookend the script element start-tag to < and >, respectively, before using it.

The discussion below begins with an analysis of the "Scrolling JavaScript" script. Subsequently, we'll clean the script up a bit and then roll out a cross-browser demo.

Scroll mechanics

As for the "JavaScript Ticker Tape" script, Joe offers no deconstruction for the "Scrolling JavaScript" script - there's some discussion vis-à-vis the script's practical implementation, but that's it - and thus it falls to us to explicate the script's mechanism of action. Fortunately, the "Scrolling JavaScript" script is a more straightforward and less cluttered affair than is its "JavaScript Ticker Tape" counterpart. The "Scrolling JavaScript" script's script element comprises a single function, scrollit_r2l( ), which Joe chose to trigger with an onload event handler:

<body background="#######" onload="timerONE = window.setTimeout('scrollit_r2l(10);', 100);">

When the script document has loaded, scrollit_r2l( ) is called after a 100-ms delay and is also passed the number 10, which is assigned to a seed variable.

function scrollit_r2l(seed) { ... }

seed is the secondary index mentioned earlier; seed is analogous to the tt_c variable in the "JavaScript Ticker Tape" script but runs in reverse, as detailed below. Before putting seed to work, scrollit_r2l( ) declares a series of variables:

var m1 = " FIRST LINE OF TEXT HERE ";
var m2 = " SECOND LINE OF TEXT HERE ";
var m3 = " THIRD LINE OF TEXT HERE ";
var msg = m1 + m2 + m3;
var out = " ";
var c = 1;


• As originally written, the script is set up to scroll three (presumably short) text messages, which are respectively assigned to m1, m2, and m3 variables, which in turn are concatenated and then assigned to an msg variable. This code can be configured at will: any number of messages (surrounded by any number of space characters) can be combined and assigned to msg (we won't see the individual m1/m2/m3 variables after this point); it is not true that [t]his script requires ... three lines to have something.

• The scroll output is appropriately given an out identifier; out is initialized to a single space character.

• The c variable will be used to build the scroll's space zone; c's declaration here is unnecessary and can be removed.

The rest of scrollit_r2l( ) consists of a series of conditionals that are separately operative depending on the value of seed. Here's the first of these conditionals:
if (seed > 100) {
	seed--;
	var cmd = "scrollit_r2l(" + seed + ")";
	timerTwo = window.setTimeout(cmd, 100); }
This conditional does nothing more than decrement seed to 100 and re-call scrollit_r2l( ) if seed is greater than 100. In fact, 100 is as high as seed gets in the script and therefore the above code can be thrown out; if you do so, be sure to also throw out the else keyword that begins the second scrollit_r2l( ) conditional, which is given below:
else if (seed <= 100 && seed > 0) {
	for (c = 0; c < seed; c++) {
		out += " "; }
	out += msg;
	seed--;
	var cmd = "scrollit_r2l(" + seed + ")";
	window.status = out;
	timerTwo = window.setTimeout(cmd, 100); }
In the first run of the scroll, this conditional is executed ten times. Here's what happens in the first of these iterations:

(1) With seed at 10, the for loop appends 10 spaces to out, thereby creating an 11-space space zone for the scroll (not counting the two spaces at the beginning of msg).
(2) The msg text zone is appended to out.
(3) out's value
FIRST LINE OF TEXT HERE SECOND LINE OF TEXT HERE THIRD LINE OF TEXT HERE
is written to the browser window's status bar (for some browsers but not others - vide infra).
(4) seed is decremented to 9.
(5) After a 100-ms delay, scrollit_r2l( ) is re-called and passed the new value of seed; upon re-calling scrollit_r2l( ), out is reset to a single space.

In the second of these iterations: the for loop creates a 10-space space zone; msg is appended to the space zone and the resulting string is outputted to the status bar; seed drops to 8; after a 100-ms delay, scrollit_r2l( ) is re-called and passed seed; out is reset to a single space.

This cycle continues until seed hits 0; in the subsequent scrollit_r2l( ) run, control passes to the next scrollit_r2l( ) conditional:
else if (seed <= 0) {
	if (-seed < msg.length) {
		out += msg.substring(-seed, msg.length);
		seed--;
		var cmd = "scrollit_r2l(" + seed + ")";
		window.status = out;
		timerTwo = window.setTimeout(cmd, 100); }
In each run of the scroll, this conditional is executed msg.length times. During these iterations, seed continues to fall, decrementing stepwise until it reaches -msg.length; as seed decrements, -seed, which rises, is used to delimit the left end of the scroll. Continuing to use Joe's dummy text for our msg value, here's what happens in the first iteration:

(1) With seed at 0, -seed is also 0 and msg.substring(0, msg.length); returns the intact msg string, which (2) is appended to out, which had been set to a single space in the wake of the seed > 0 conditional's last scrollit_r2l( ) call.
(3) out's value
FIRST LINE OF TEXT HERE SECOND LINE OF TEXT HERE THIRD LINE OF TEXT HERE
is written to the status bar.
(4) seed drops to -1; (5) after a 100-ms delay, scrollit_r2l( ) is re-called and passed seed; out is reset to a single space.

In the 10th iteration: seed begins at -9 and -seed at 9; msg.substring(9, msg.length); returns INE OF TEXT HERE SECOND LINE OF TEXT HERE THIRD LINE OF TEXT HERE ,
which is appended to the out space; out is sent to the status bar - note that the out output always begins with a space, even as the scroll's text zone is whittled down; seed drops to -10; after a 100-ms delay, scrollit_r2l( ) is re-called and passed seed; out is reset to a single space.

This cycle continues until seed hits -89; in the subsequent scrollit_r2l( ) run, control passes to the last scrollit_r2l( ) conditional:
else {
	window.status=" ";
	timerTwo = window.setTimeout("scrollit_r2l(100);", 75); } }
A single space is written to the status bar and then scrollit_r2l( ) is re-called and passed 100 after a 75-ms delay to begin the next scroll run. The beginning seed value for the second and subsequent scroll runs is 100 and the seed > 0 conditional for these runs will be executed 100 times (the conditional's for loop will generate a 101-space space zone, a 100-space space zone, a 99-space space zone, etc.).

Conditional retool

Here's how I would rewrite scrollit_r2l( )'s conditional code:
if (0 < seed) { 
	for (var c = 0; c < seed; c++) { out += " "; }
	out += msg; }
else if (-seed < msg.length) {
	out += msg.substring(-seed, msg.length); }
seed--;
window.status = out;
if (-seed == msg.length) {
	seed = 100;
	window.status = " "; }
window.setTimeout("scrollit_r2l( );", 100);
Comments

• For positive values of seed, the if (0 < seed) clause is operative, even as -seed will be less than msg.length for these values.

• In the original script, each scrollit_r2l( ) conditional ends with a delayed and parameterized call to scrollit_r2l( ). As shown above, this call can be 'factored out', i.e., written once at the end of scrollit_r2l( )*. We can unparameterize** the call if we globally initialize seed to 10

var seed = 10;

function scrollit_r2l( ) { ... }


and set seed to 100 in the last scrollit_r2l( ) conditional. Finally, I've thrown out the window.setTimeout( ) assignment to the timerTwo variable, of which no use is made in the script.

*The user will have to wait another 25 milliseconds in going from one scroll run to the next, but that's life.
**Admittedly, the var cmd = "scrollit_r2l(" + seed + ")"; statement is instructive in that it illustrates how to parameterize a window.setTimeout( ) command, but again, parameterization is unnecessary here.

• The seed--; and window.status = out; lines originally appear in both the second and third scrollit_r2l( ) conditionals; I've factored those guys out too.

• The last iteration of the -seed < msg.length conditional originally ends with a scrollit_r2l( ) call; the above code eliminates this call, which is redundant/unnecessary because the last scrollit_r2l( ) conditional itself sets window.status to a space.

Status detour

As in Script Tips #35 and #36, the "Scrolling JavaScript" script outputs its scroll to the browser window's status bar. However, the "Summary" section of Mozilla's current window.status page warns:
This property does not work in the default configurations of Firefox and some other browsers: setting window.status has no effect on the text displayed in the status bar. To allow scripts to change the status bar text, the user must set the dom.disable_window_status_change preference to false in the about:config [pane].
Joe runs the script at the tutorial page; in practice on my computer, I see his scroll when using Safari, Opera, or MSIE, but not when using a Mozilla-based browser (Firefox, Camino, Netscape 9) or Chrome.

Of course, we have other options for outputting a scroll. Directing out to a text box would be the classical thing to do, although my own preference is to load out into a div element per the "Format me" section of Blog Entry #187. So, for those of you who can't see Joe's scroll in the status bar at the tutorial page, here's a div-based version of what you're missing (OK, I've tidied Joe's messages up a bit):


There's not much to the next two Beyond HTML : JavaScript tutorials - "Print A Web Page Using JavaScript" and "So, You Want A Live Script, Huh?" - but each of them deserves some brief commentary, so maybe we can cover both of them in the following entry.

reptile7

Friday, August 20, 2010
 
Double-Flip Your Pleasure
Blog Entry #188

We have discussed 'image flipping' and its applications several times previously, and we'll do so again today with a look at HTML Goodies' "So, You Want A Dual Image Flip, Huh?" tutorial.

Like the "So, You Want A SlideShow, Huh?" tutorial we covered earlier in the Beyond HTML : JavaScript sector, "So, You Want A Dual Image Flip, Huh?" offers a script and a set of images for creating a simple slide show. Joe doesn't describe the effect of the latter tutorial's script as a "slide show", but the term is apropos as we'll be loading a series of images into a common img placeholder.

The user moves through the "So, You Want A SlideShow, Huh?" slide show by clicking Next and Back links. The "So, You Want A Dual Image Flip, Huh?" slide show's interface is a bit more involved:
(a) Each new slide is loaded into the slide show placeholder via its own "button": not an actual push button but a link whose content is an image of a button.
(b) Upon mousing over the link, the current slide changes to a new slide, and the link's button image changes to a pressed-button image as though you have pressed the original "button" - that's the "dual" part of the script.

Joe has conveniently combined the tutorial's script and various images in a .zip file that is posted here, and he provides a functioning demo for the script in the tutorial's introduction.

The images

OK, what do we have here? Let's begin with the img placeholder that the slide show's images will be loaded into:

<img name="global" width="130" height="130" src="white.gif" alt="[ ]" />

The script will reference this placeholder via its global name, more specifically, with document.images["global"]; the button image placeholders discussed below are also referenced in this way. Today the W3C would want us to change the img name attributes to id attributes and then reference the placeholders with document.getElementById("imgID") - indeed, the name attribute of the img element has been deprecated by XHTML 1.0 - but you have to remember when HTML Goodies' JavaScript scripts were written (most of them in the late 1990s); as late as Netscape 4.x, Netscape did not support the id attribute for the img element or for most other elements.

We will load three images into the global placeholder:

(1) A white.gif image (this is the file name that appears in the tutorial's "The Code" section and in the imagetwo.htm document containing the script in the dualflip.zip package, but at some point the file name was changed to dualflip.gif for the tutorial demo):
['The Goodies/Click Here']

(2) An hg_banner.gif image: ['HTML Goodies/developer.com'] (3) A jg_banner.gif image: ['Java Goodies/developer.com']

Although the white.gif image says (inter alia) "CLICK HERE" and the hg_banner.gif and jg_banner.gif images sport the developer.com domain name, clicking these images won't take you anywhere; unlike the button image placeholders, the global placeholder is not wrapped in a link.

To the left of the global placeholder - we'll have more to say about the script's display layout later - are two placeholders for the aforementioned button images that seem* to trigger global image flipping (*as we'll see in the next section, the 'heavy lifting' is actually done by the placeholders' anchor element parents):

<img name="one" width="96" height="36" border="0" src="but1.gif" alt="[ ]" />
...
<img name="two" width="96" height="36" border="0" src="but2.gif" alt="[ ]" />


The one placeholder initially holds a but1.gif image:
['Button 1', unpressed]
(In the dualflip.zip package, this file is named but1.GIF: be sure to lowercase the GIF to gif before using this file.)

Note that but1.gif has a small decoration in its upper-left-hand corner that makes the image's button look as though it were coming out of the screen. Mousing over but1.gif flips white.gif, the first global slide, to hg_banner.gif, and also flips but1.gif itself to a but1b.gif image:
['Button 1', pressed]

but1b.gif is a 'pressed' version of but1.gif: by
(a) subtracting the upper-left-hand corner decoration,
(b) cutting the button's 'borders' in half, and
(c) slightly shifting the "Button 1" label's top offset,
the "Button 1" button now looks like it's been pushed into the screen. Mousing out from but1b.gif flips hg_banner.gif back to white.gif and but1b.gif back to but1.gif.

Analogously, the two placeholder initially holds a but2.gif image
['Button 2', unpressed]
that when mouseovered flips white.gif to jg_banner.gif and itself to a 'pressed' but2b.gif image
['Button 2', pressed]
and vice versa when but2b.gif is mouseouted.

Before white.gif, but1.gif, and but2.gif are loaded into their respective placeholders, all seven images are preloaded by the following statements at the top of the script's script element:

alt0 = new Image( ); alt0.src = "white.gif";
alt1 = new Image( ); alt1.src = "hg_banner.gif";
alt2 = new Image( ); alt2.src = "jg_banner.gif";
graphic1 = new Image( ); graphic1.src = "but1.gif";
graphic1on = new Image( ); graphic1on.src = "but1b.gif";
graphic2 = new Image( ); graphic2.src = "but2.gif";
graphic2on = new Image( ); graphic2on.src = "but2b.gif";


As a dial-up user myself, I can confirm that the above code will definitely speed up the first "Button 1"/"Button 2" image flips for those with slow Web connections.

Flip mechanics

For all of the img placeholders, image flipping is carried out by the imageChange( ) function that follows the preloading code:
function imageChange(imageID, imageName, imageID2, imageName2) {
	document.images[imageID].src = eval(imageName + ".src");
	document.images[imageID2].src = eval(imageName2 + ".src"); }
The imageChange( ) parameter names are a bit misleading; in practice, here's what we'll be feeding to imageChange( ):

• In all cases, imageID will be global, the name of the slide show placeholder.
imageName will be an object reference, set by the preloading code, for one of the global images: alt1, alt0, or alt2.
imageID2 will be the name of one of the button image placeholders, i.e., one or two.
imageName2 will be an object reference, again set by the preloading code, for one of the button images: graphic1on, graphic1, graphic2on, or graphic2.

So whereas names are indeed identifiers and object references can informally be viewed as 'names', the imageChange( ) declaration should be written as:

function imageChange(imgName, imageObject, imgName2, imageObject2)

The one and two button image placeholders are wrapped in anchor elements equipped with onmouseover and onmouseout event handlers that call imageChange( ) and supply it with img name and image object reference inputs:

<a href="/" onmouseover="imageChange('global', 'alt1', 'one', 'graphic1on');" onmouseout="imageChange('global', 'alt0', 'one', 'graphic1');">
<img name="one" width="96" height="36" border="0" src="but1.gif" alt="[ ]" /></a><br /><br />

<a href="http://www.javagoodies.com" onmouseover="imageChange('global', 'alt2', 'two', 'graphic2on');" onmouseout="imageChange('global', 'alt0', 'two', 'graphic2');">
<img name="two" width="96" height="36" border="0" src="but2.gif" alt="[ ]" /></a>


You may be thinking, "Why don't we ditch the links and then put the onmouseover and onmouseout event handlers in the img tags?" To be sure, we could do that today, but let me put on my archivist's hat once again and point out that pre-Gecko versions of Netscape only supported the onmouseover and onmouseout attributes for the anchor, area, layer, and ilayer elements - that's why the links are there.

Historical note: The link wrapper for the two placeholder points to http://www.javagoodies.com, Joe's one-time "Java Goodies JavaScript Repository". (Some of the www.javagoodies.com scripts are still there and we may be mining this material in the future.) As an independent site, www.javagoodies.com disappeared sometime in late 1999/early 2000 and now redirects to www.developer.com/java/ ("gamelan.com").

All that remains for us to do, deconstruction-wise, is to plug the onmouseover- and onmouseout-passed values into imageChange( ) and verify that the resulting assignments produce the image flips described in the preceding section. I trust you're up to the task of doing that, but we can quickly go through one example. Mousing over the links[0] link effectively generates the following imageChange( ) statements:

document.images["global"].src = eval("alt1" + ".src");
document.images["one"].src = eval("graphic1on" + ".src");


Concatenation of the strings "alt1" and ".src" gives the string "alt1.src", which is evaluated with the eval( ) function to give "hg_banner.gif", which is then assigned to the src property of the document.images["global"] placeholder, thereby replacing white.gif with hg_banner.gif. Similarly, concatenation of "graphic1on" and ".src" gives "graphic1on.src", which is evaluated with eval( ) to give "but1b.gif", which is assigned to the src property of the document.images["one"] placeholder, thereby replacing but1.gif with but1b.gif.

The eval( ) function is not unproblematic, and imageChange( )'s use thereof can be eliminated if, instead of concatenating object reference strings with ".src", we were to concatenate the corresponding pre-extension file name strings with ".gif":
function imageChange(imgID, imageFileName, imgID2, imageFileName2) {
	document.images[imgID].src = imageFileName + ".gif";
	document.images[imgID2].src = imageFileName2 + ".gif"; }
...
<a href="/" onmouseover="imageChange('global', 'hg_banner', 'one', 'but1b');" onmouseout="imageChange('global', 'white', 'one', 'but1');">
...
<a href="/" onmouseover="imageChange('global', 'jg_banner', 'two', 'but2b');" onmouseout="imageChange('global', 'white', 'two', 'but2');">
...
<!-- For that matter, the intact myImage.gif URLs themselves could be fed to imageChange( ), if you'd rather do that. -->
Layout, and a demo

Joe lays out his demo in a one-row, two-cell table - the one and two img placeholders and their link wrappers are placed in the left cell, and the global img placeholder is placed in the right cell - but as we all know, the W3C frowns on the use of tables for document layout. The state-of-the-art way to achieve Joe's layout (to which you of course are not restricted, as Joe himself points out) is to load everything into a div element with an appropriate width and then float the global placeholder right.

We wrap up this post with a demo. In the div below, move your mouse cursor over the "Meow" or "Woof" button to flip the parrot photo to a new photo:

[ ]


My demo uses real buttons, and not images thereof - you shouldn't have to go running to a graphics editor to make slide controls. I mimic the un/pressed business by toggling the buttons' borders between 7px outset #ff00ff (unpressed) and 3px inset #ff00ff (pressed). Push buttons are typically clicked and not mouseovered, and the demo script can be set up so that alternate clicks toggle simultaneously a button's border and the global image, but I prefer the mouseover/mouseout interface in this case, so I stuck with it.

I myself don't see the point in returning to the original global image upon mouseouting the slide controls, so I simplified the controls' onmouseout code accordingly:
onmouseout="this.style.border = '7px outset #ff00ff';"

A final thought:
I find it a bit surprising that Joe didn't play up the each-slide-has-its-own-control aspect of the "So, You Want A Dual Image Flip, Huh?" script, as this type of slide show would be generally useful for illustrating the items of a list - a list of PCs for sale, a list of vintage automobiles, a list of dog breeds, whatever. Let's say you run a Web site dedicated to your favorite musical artist. Your site features a "Discography" page listing the artist's album releases; mousing over each album name could load a reproduction of the album's front cover into an img placeholder to the right of the album list.

The next Beyond HTML : JavaScript tutorial, "So, You Want An External JavaScript, Huh?", is too basic to discuss - I trust y'all know how to externalize a script via a <script type="text/javascript" src="myScript.js"></script> element - so we won't be discussing it. The tutorial after that, "So, You Want A Scrolling JavaScript, Huh?", serves up another scrolling text script. Do we really want to go through one of those guys again so soon in the following entry? Yeah, why not?

reptile7

Tuesday, August 10, 2010
 
Let's Scroll Some More
Blog Entry #187

In the previous post, we dissected the scrolling text script of HTML Goodies' "So, You Want A JavaScript Ticker Tape, Huh?" tutorial. At this point, we have the script's scroll up and running:



(As in our trial run through the move( ) function at the end of the last entry, I've given the input element a font-family:Courier; style so that the width of the scroll initially matches the size of the scroll field.)

Next issue: How would we stop the scroll, if we wanted to do that? After all, not everyone likes text scrolls; indeed, some users find them distracting and annoying...

The terminator

In the tt_start( ) function and at the end of the move( ) function itself, the main move( ) function that creates the scroll is called via the following window.setTimeout( ) command:

tt_tid = window.setTimeout("move( );", tt_speed);

The window.setTimeout( ) method returns a number* - typically a low-value integer: 0, 1, or 2 - that serves as an ID for the timeout it sets: imagine a <timeout delay="250" id="1">move</timeout> element corresponding to the above statement. (*Mozilla's window.setTimeout( ) page doesn't say anything about the timeoutID data type, although Microsoft's window.setTimeout( ) page does.) The timeout ID can be fed to a window.clearTimeout( ) command to cancel both the timeout and the code execution delayed by the timeout, and the script's main script element includes a tt_cleartid( ) function with such a command:
function tt_cleartid( ) {
	window.clearTimeout(tt_tid); }
The tt_cleartid( ) function is not called at any point in the script. But it is simple enough to add to the tickertapeform form a button that when clicked would call it:

<button type="button" onclick="tt_cleartid( );">Stop the scroll</button>

Complementarily, we could follow the button with a button that when clicked would restart the scroll by re-calling the move( ) function:

<button type="button" onclick="move( );">Restart the scroll</button>

Two more points before moving on:

(1) When delaying a function call with window.setTimeout( ), a simple functionName object reference to the function can serve as the first setTimeout( ) parameter:

tt_tid = window.setTimeout(move, tt_speed);

Mozilla recommends this syntax vs. the "move( );" stringified function call syntax.

(2) The W3C has brought the setTimeout( ) and clearTimeout( ) methods into HTML5.

Cleanup

Including the tt_cleartid( ) function and not counting the first ticker( ) function, the main script element contains four functions, in source order: move( ), tt_start( ), tt_cleartid( ), and ticker( ). We definitely need the move( ) function to create the scroll, but the other three functions can be thrown out, if desired.

The move( ) function is originally called by the tt_start( ) function. To get rid of the tt_start( ) function:
(1) Globally declare the tt_f identifier for the tickertapeform form.
(2) Move tt_start( )'s move( ) function call to the end of the script element.

The tt_start( ) function is called by the second ticker( ) function. To get rid of the second ticker( ) function:
(1-3) Globally set tt_message, tt_len, and tt_speed to their move( ) values.
(4-5) Write the tickertapeform form as normal HTML as described in the previous post, and then delete the tt_start( ) function call.

var tt_f = document.tickertapeform;
/* With an eye on XHTML compliance, you might prefer to remove the form's name attribute and write the above declaration as: var tt_f = document.forms[0]; */

var tt_message = "The rain in Spain falls mainly on the plain...";
var tt_len = 35;
var tt_speed = 250;
...
function move( ) { ... }

move( ); /* Is there a need to delay the initial move( ) call? Nah. */


The tt_cleartid( ) function is a nice extra but is unnecessary; chuck it if the ability to stop the scroll is not a priority for you.

That leaves us with just the move( ) function, which, vis-à-vis its appearance on the tickertp.txt page, can itself be slightly tightened up by combining its if (tt_c < 0) and else clauses as shown below. Moreover, move( )'s cend variable can be eliminated, as there's no real need to bound the scroll at tt_c + tt_len for relatively long tt_messages (at least I'm unaware of any limitation on a text box's value.length, which is not to say that creating a really long scroll is a good idea - it isn't); if tt_message extends beyond the right edge of the scroll field, no big deal.
function move( ) {
	if (tt_c < 0) { // If tt_c is in the 'space zone':
		cstart = 0;
		tt_f.scroll.value = tt_space.substring(0, -tt_c) + tt_message.substring(cstart, tt_message.length); }
	else { // If tt_c is in the 'text zone':
		cstart = tt_c;
		tt_f.scroll.value = tt_message.substring(cstart, tt_message.length); }
	tt_c = tt_c + 1;
	if (tt_c == tt_message.length) tt_c = -tt_len;
	tt_tid = window.setTimeout(move, tt_speed); }
The move( ) function could be shrunk significantly if we were to apply it to a concatenated tt_space + tt_message string à la the Script Tips #35-37 script, e.g.:
var scrollText = tt_space + tt_message;

function move( ) {
	scrollText = scrollText.substring(1, scrollText.length);
	tt_f.scroll.value = scrollText;
	if (scrollText.length == 1) scrollText = tt_space + tt_message;
	tt_tid = window.setTimeout(move, tt_speed); }
Format me

In the tutorial's "Go Faster You Script You!" section, Joe laments, You cannot change the scrolling text to bold or italic or make it bigger using this script. This script is a very simple scroll and you'd need a more fancy piece to play with the text; relatedly, tutorial commenters JohnS and Steeeeve ask about coloring the scroll text. The b, i, and font elements can validly have input element children by virtue of their (%inline;)* content model, but it doesn't follow therefrom that these elements will have any effect on text box values as such values do not constitute element content, and in practice they don't. (One exception: I find that Opera applies the i element to both text and textarea field values.)

I don't know when exactly the "So, You Want A JavaScript Ticker Tape, Huh?" script was written, but given that its main script element is wrapped in an xmp element, whose use the W3C was discommending as early as HTML 2.0, it clearly goes back pretty far and probably predates the first CSS and DOM specifications. Gratifyingly, modern browsers generally apply CSS's font properties and color property to text input values, and therefore an

input { color: red; font: bold italic 24px; }

style rule set is all we would need to create a red, bolded, italicized, double-sized scroll.

But as we have noted several times previously, CSS 2.1 does not define which properties apply to form controls ... or how CSS can be used to style them - what if the user's browser won't apply the above CSS to the scroll field? Once upon a time (more specifically, for pre-'level 4' browsers), a text input was pretty much the only possible 'standard output' to which a scroll could be directed. Fortunately, the DHTML innerHTML property and the DOM textContent property will now allow us to load a scroll into any can-contain-#PCDATA element, in particular a div or p element, to which the CSS font and color properties unambiguously applies.

#div0 { color: red; font: bold italic 24px; white-space: pre; }
...
<div id="div0"></div>
...
document.getElementById("div0").textContent = scrollText;


There's just one little catch in doing this: in order to prevent the collapse of the tt_space substring to a single space, be sure to add a white-space:pre; declaration to your CSS.

Commenter Steeeeve also asks about removing the scroll field's border. A border:0px; style declaration will work for a text box, but a div or p 'field' won't have a border in the first place.




A final point:
The tutorial's concluding subsection asks, "Can I Get A Second Script On The Same Page?" Joe answers No but I say yes - just be careful about your use of function and variable identifiers, and it's all good...

We'll move on to "So, You Want A Dual Image Flip, Huh?", the next Beyond HTML : JavaScript tutorial, in the following entry.

reptile7

Sunday, August 01, 2010
 
Let's Scroll
Blog Entry #186

In today's post, we will check over the scrolling text script of HTML Goodies' "So, You Want A JavaScript Ticker Tape, Huh?" tutorial.

We previously went through a scrolling text script in HTML Goodies' JavaScript Script Tips #35-37, which were covered in Blog Entry #65. (In Script Tips #35-36 the script writes its scroll to the browser window's status bar whereas in Script Tip #37 the script loads its scroll into a text box, but these scripts are identical vis-à-vis creating the scroll.) The Script Tips #35-37 script and the "So, You Want A JavaScript Ticker Tape, Huh?" script have the same effect - in both cases, a text string preceded by a multispace 'buffer' scrolls from right to left in a containing field - but somewhat different modes of operation.

In brief, the Script Tips #35-37 script
(a) copies the scroll string's leftmost character and pastes it onto the right end of the scroll string,
(b) cuts the scroll string's leftmost character, and then
(c) shifts all of the scroll string's characters one position to the left;
(d) do this over and over and you've got a scroll.
As regards this sequence of operations, the "So, You Want A JavaScript Ticker Tape, Huh?" script omits step (a) and carries out steps (b-d); when the scroll string has been whittled down to its last character - when it appears to have been (almost) pushed beyond the left edge of its field - the script resets itself via resurrecting the original scroll string.

Most of the "So, You Want A JavaScript Ticker Tape, Huh?" script is posted on this page; the script's ticker( ) function trigger is detailed in the tutorial's "Activating the Script" section. Joe provides a functioning demo for the script at the top of the tutorial.

Joe's exhortations to the contrary notwithstanding, the "So, You Want A JavaScript Ticker Tape, Huh?" script can be 'messed with' quite a bit, as we'll see later.

Deconstruction

Before we get started:
You'll notice on the tickertp.txt page that either Joe or one of the script's authors has wrapped the script in an xmp element towards the end of preserving the script's whitespace formatting. I trust y'all know that the xmp element was obsoleted long ago, and that you should now use the pre element to do this sort of thing.

Setting the stage: HTML and global variables

As in Script Tip #37, the "So, You Want A JavaScript Ticker Tape, Huh?" script displays its scroll in a text box. The box and its form container are coded by a document.write( ) command in the script's second ticker( ) function:

document.write("<form name='tickertapeform'><input name='scroll' size='35'></form>");

Alternatively and preferably, the tickertapeform form and its scroll box can be written as normal (non-script) HTML as long as the form is placed before the main script element in the source:

<form name="tickertapeform" action="">
<input type="text" name="scroll" size="35">
</form>
<script type="text/javascript">
<!-- Ticker Tape in JavaScript .. Heavily Modified by Bill Welliver
...


Before the scrolling action gets under way, the script declares the following global variables:
var tickertapeform;
var tt_speed = 350;
var tt_len = 35;
var tt_space = " "; // 100 spaces
var tt_tid = 0;
var tt_message = ".";
var tt_c = -tt_len;
• A tickertapeform variable is declared but not initialized; this variable does not appear subsequently in the script and its declaration can be removed.

tt_speed will represent the scroll's 'refreshment rate'; more specifically, the scroll will be updated every tt_speed milliseconds. Although initially set to 350, tt_speed doesn't need to be initialized at this point as we'll be (re)setting it in a bit.

tt_len represents the size of the scroll's field, i.e., of the scroll box; you might prefer to write this declaration as:
var tt_len = document.tickertapeform.scroll.size;
/* Really old browsers (e.g., Netscape 4.x) will not support the DOM size property, but that's their problem. */


• The aforementioned multispace buffer that precedes the scroll's text string will be extracted from the 100 spaces to which tt_space is set. I myself would have used a for loop to define tt_space:
var tt_space = "";
for (i = 0; i < 100; i++) { tt_space += " "; }
/* Or, given that 100 spaces is clearly overkill and that the scroll size is 35: */
for (i = 0; i < tt_len; i++) { tt_space += " "; }


tt_tid could (presently does not) serve as a numeric identifier for stopping the scroll - we'll have more to say about this when we discuss the script's tt_cleartid( ) function.

tt_message will represent the scroll's text string. Although initially set to . (a period), tt_message doesn't need to be initialized at this point as we'll be (re)setting it in a bit.

• A counter variable of sorts, tt_c plays a central role in determining the scroll's display - we'll have more to say about it when we discuss the script's move( ) function.

Initiation

Let's get scrolling, shall we? The script is set in motion by a call to a ticker( ) function. Joe places this call in a separate script element

<script type="text/javascript">
ticker("THE SCRIPT GOES HERE");
</script>


following the script's main script element; equivalently, the ticker( ) function call could be placed at the end of the main script element or triggered by an onload event handler in the body element start-tag. "THE SCRIPT GOES HERE", the ticker( ) function call parameter, will be the scroll's text string.

You'll notice I said "a" ticker( ) function and not "the" ticker( ) function: that's because there are actually two ticker( ) functions in the main script element, something Joe strangely does not comment on.
function ticker(m, l, s) {
tt_message = m;
tt_len = l;
tt_speed = s;
document.write("<form name='tickertapeform'><input name='scroll' size='");
document.write(tt_len);
document.write("' value=''></form>");
tt_start(document.tickertapeform); }

function ticker(m) {
tt_message = m;
tt_len = 35;
tt_speed = 250;
document.write("<form name='tickertapeform'><input name='scroll' size='35'></form>");
tt_start(document.tickertapeform); }
As shown above, the first ticker( ) function expects to be passed three parameters whereas the second ticker( ) function expects only one parameter. In line with our one-parameter ticker( ) function call, the second ticker( ) function is the one that we want, and is the one that is called in practice by virtue of it appearing later in the source. The first ticker( ) function can therefore be thrown out, although changing the ticker( ) function call to ticker("THE SCRIPT GOES HERE", 35, 250); would allow you to route the script through the first ticker( ) function and throw out the second ticker( ) function if you'd rather do that.

In the second ticker( ) function:
(1-3) THE SCRIPT GOES HERE, 35, and 250 are assigned to tt_message, tt_len, and tt_speed, respectively.
(4) Next, the scroll text box and its form container are written to the page - this line should be removed if the form is written as normal HTML as described earlier.
(5) The last line calls the script's tt_start( ) function and passes thereto document.tickertapeform, a reference to the tickertapeform form.

So, let's move to the tt_start( ) function:
function tt_start(x) {
tt_f = x;
tt_tid = window.setTimeout("move( );", tt_speed); }
In the tt_start( ) function:
(1) document.tickertapeform is given a tt_f identifier.
(2) The script's move( ) function is called after a delay of 250 (tt_speed) milliseconds.

Propagation

The move( ) function is where the scrolling action happens, and is the only one of the script's functions that is really necessary. Like the SBScroll( ) function of the Script Tips #35-37 script, the move( ) function sets and changes the composition of its scroll in a looplike, if more complex, manner. Here it is:
function move( ) {
cend = Math.min(tt_c + tt_len, tt_message.length);
if (tt_c < 0)
cstart = 0;
else
cstart = tt_c;
if (tt_c < 0)
tt_f.scroll.value = tt_space.substring(0, -tt_c) + tt_message.substring(cstart, cend);
else
tt_f.scroll.value = tt_message.substring(cstart, cend);
tt_c = tt_c + 1;
if (tt_c == tt_message.length)
tt_c = -tt_len;
tt_tid = window.setTimeout("move( );", tt_speed); }
Prior to calling the SBScroll( ) function, the Script Tips #35-37 script prepends 18 spaces to its text string to give a larger spaces+text string, which is subsequently manipulated by the SBScroll( ) function. In contrast, the "So, You Want A JavaScript Ticker Tape, Huh?" script keeps its tt_space 'space zone' and tt_message 'text zone' separate, and the move( ) function creates its scroll by acting on these zones separately - this is ultimately why move( ) is more complicated than it needs to be.

The space zone and text zone characters that make up the move( ) scroll are directly or indirectly determined by tt_c, a secondary index that supplements the normal 0-to-stringName.length-1 indexing of a JavaScript string. The tt_c index traverses the entire length of the space and text zones; its value ranges from -35 for the first space character of the space zone to tt_message.length for the 'null character' that would follow the final character of the text zone.

As tt_c moves through the space zone,
(1) tt_space.substring(0, -tt_c) returns a decrementing number of spaces, and
(2) tt_message.substring(cstart, cend) returns an initially incrementing number of text characters, which
(a) maxes out at the total number of text characters if the text string's length is less than the size of the scroll field, but
(b) continues to increment, up to a maximum of 35, if the string's length exceeds the scroll size.
These two groups of characters are concatenated to give the scroll. At any given time, no more than 35 scroll characters appear in the scroll field (even if you're using a sans-serif font, which can place more than 35 characters in a size="35" box).

As tt_c moves through the text zone, the scroll is simply given by tt_message.substring(cstart, cend), which, depending on the text string's length, returns 35 or fewer text characters and eventually or immediately shrinks to a single (the last tt_message) character, as detailed below.

Left and right boundaries for the range of the scroll's text string that is displayed at a given time are respectively set by cstart and cend variables. cstart is initialized to 0 and remains at 0 (i.e., is anchored to the first tt_message character) as tt_c moves through the space zone; cstart is set to tt_c and increments along with tt_c as tt_c moves through the text zone. For all tt_c values, cend is set by the following statement:

cend = Math.min(tt_c + tt_len, tt_message.length);

This unintuitive command - not so easy to get a handle on - adds 35 (tt_len) to the tt_c index and then compares the resulting index to tt_message's length; in practical terms:
(a) if tt_c + tt_len is less than tt_message.length, then the scroll value (tt_f.scroll.value) will end ('cend') somewhere in the interior of tt_message and will span the entire scroll size, i.e., there are more characters to the right that have yet to scroll into view, whereas
(b) if tt_message.length is less than tt_c + tt_len, then the scroll value will cend with tt_message's final character and will not span the entire scroll size, i.e., it'll be followed by empty space, as the right end of tt_message is now to the left of the right edge of the scroll field.

Now, to really see what's going on in the move( ) function, we need a test tt_message string; we could use Joe's THE SCRIPT GOES HERE dummy text for this purpose, but a string whose length exceeds the size of the scroll field would be a better choice. Accordingly, we will in the discussion below apply move( ) to The rain in Spain falls mainly on the plain..., for which tt_c will undergo a cycle of 82 values - -35 to 46, inclusive - over 81 move( ) 'iterations'. tt_c's value maps to the space zone for the first 35 move( ) iterations and to the text zone for the remaining move( ) iterations.

In the 1st move( ) iteration, tt_c begins at -35 (-tt_len), cstart is 0, and cend is 0; consequently, 35 tt_space spaces + 0 tt_message characters are written to the scroll field:

tt_c is incremented to -34 and move( ) is re-called after a 250-ms delay.

In the 10th move( ) iteration, tt_c begins at -26, cstart is 0, and cend is 9; 26 tt_space spaces + the first 9 tt_message characters are written to the scroll field:

tt_c is incremented to -25 and move( ) is re-called after a 250-ms delay.

In the 36th move( ) iteration, tt_c begins at 0, cstart is 0 for the last time in the cycle, and cend is 35. We are now in the text zone and move( )'s else clauses are operative. The first 35 tt_message characters are written to the scroll field:

tt_c is incremented to 1 and move( ) is re-called after a 250-ms delay.

In the 47th move( ) iteration, tt_c begins at 11, cstart is 11, and cend is 46 (tt_message.length), where it will stay for the rest of the cycle. The last 35 tt_message characters are written to the scroll field:

tt_c is incremented to 12 and move( ) is re-called after a 250-ms delay.

In the 72nd move( ) iteration, tt_c begins at 36, cstart is 36, and cend is 46. The last 10 tt_message characters are written to the scroll field:

tt_message's concluding ellipsis is followed by empty space, and not by space characters as would be so for the Script Tips #35-37 script.
tt_c is incremented to 37 and move( ) is re-called after a 250-ms delay.

In the 81st and last move( ) iteration of the cycle, tt_c begins at 45, cstart is 45, and cend is 46. The last tt_message character is written to the scroll field:

tt_c is incremented to 46 and now equals tt_message.length, so it is reset to -35 (-tt_len) by move( )'s third if statement; move( ) is re-called after a 250-ms delay to begin the next cycle.

That's how the script works as it appears on the tickertp.txt page. In the following entry, we'll first discuss how to stop (and restart) the scroll and then see if we can't clean the script up by getting rid of its unnecessary code.

reptile7


Powered by Blogger

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