PDA

View Full Version : RainbowText v2.0



ChrisRickard
02-19-2008, 05:12 AM
1) CODE TITLE: RainbowText v2.0

2) AUTHOR NAME/NOTES: Chris Rickard.

3) DESCRIPTION: Highlights the individual colors of the given text at random

4) URL TO CODE: http://www12.brinkster.com/chrisrickard/development/Rainbow/

Note that this is an upgrade from the very old DD script
http://www.dynamicdrive.com/dynamicindex10/rainbow.htm

Note that also I'm the original author and the address listed on the old page (chris.rickard@paccoast.com) is no longer valid. (Not for years! :D)

jscheuer1
02-19-2008, 06:43 AM
There is no cr tag.

ChrisRickard
02-19-2008, 08:32 AM
I register the cr namespace in Rainbow.js lines 186-203

jscheuer1
02-19-2008, 08:50 AM
Mmm, well the page doesn't validate, and that is just one of the errors. I don't think you can register a namespace for a quirksmode HTML page.

ChrisRickard
02-19-2008, 09:21 AM
I'm curious, does it not work for you? :confused:

I've tested it with Internet Explorer 5.5-7, Firefox 2, Opera 9, Safari 3, and Konqueror 4 in combination with full quirks mode, HTML 4 Transitional, HTML 4 Strict, XHTML Transitional, and XHTML Strict doctypes.

While the example page given doesn't validate because the script does the namespace import there is nothing stopping the end developer from merely putting in a xmlns:cr="http://www12.brinkster.com/chrisrickard" in the html element to make it validate. I wrote it to work with or without the namespace declared in markup because the typical user of this script probably has no clue as to what namespaces and validation are, they just want the script to work with a minumum amount of effort.

jscheuer1
02-19-2008, 09:47 AM
You would be surprised how many folks are obsessing with validation these days, I'm tempted to say about a third of the questions we field here are on how to get this or that script or the person's own HTML or CSS code to validate, but I'm really not sure, it is quite a few.

Isn't there a simpler way of doing this that doesn't involve namespaces? Couldn't you just have a class? Relying on code that requires xml in a world where XHTML isn't supported by the majority of installed browsers just seems unwise to me - in practice it may all work out though, but only due to the error correcting nature of the browsers involved, and/or their failure to follow standards - always a sketchy premise to build one's code upon. I guess that's why you felt compelled to test it under so many conditions and browsers.

Anyways, the script works as far as I can tell, except for the, I suppose inevitable problem with Opera when using the forward and backward buttons to navigate to the page.

ChrisRickard
02-19-2008, 10:12 AM
Yeah there's two other ways I can think of going about it. First it can be done from script and uses the existing rainbow.js code



// The script can work with any element, it doesn't really have to be a cr:Rainbow
var rainbowExample = new Rainbow(document.getElementById("SomeElement"));


The second is your suggestion of using classes instead. Unfortunately this would require traversing the entire document which was something I wanted to avoid, mostly because it can take so long on bigger documents. getElementsByTagName() is really efficient in all browsers.

Twey
02-19-2008, 11:45 AM
You could also use sequential IDs, ugly though it may be.

You're relying on buggy behaviour here, since you're serving the document with the wrong MIME type -- it's actually not XHTML at all, just invalid HTML (if you did serve it as XHTML it wouldn't display at all, because it's badly formed).

ChrisRickard
02-19-2008, 07:21 PM
OK, new demo page that validates:
http://www12.brinkster.com/chrisrickard/development/Rainbow/dd.htm

Twey
02-19-2008, 09:26 PM
You've overcomplicated this in two ways: by using innerHTML, which is non-standard, bad style, and makes this sort of operation much trickier than it should be, and by making it very object-oriented, which doesn't offer much in the way of advantages here. I came into programming from the Java area, and it took me a while to realise that objects aren't the answer to everything. Closures are often (but not always) a much neater solution. Here's a less complicated (but probably fully-featured) version, just using DOM methods and closures:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Testcase</title>
<script type="text/javascript">
if (typeof Node === "undefined")
var Node = {
TEXT_NODE: 3
};

// Apply an animated "rainbow" effect to an element, with random colours.
var rainbow = (function(opts) {
var t = null, // To store the interval ID.
c = [], // To store the letters to be updated.
interval = opts.interval || 100,
coverage = opts.coverage || 5;

// Zero-pad a number.
function pad(num, places, radix) {
var s = num.toString(radix || 10);
return (new Array(places - s.length + 1)).join('0') + s;
}

// Give each letter its own <span> and add it to an array of elements to update.
function getLetters(el) {
var r = [];

if (el.nodeType === Node.TEXT_NODE) {
for (var s = el.nodeValue, i = 0; i < s.length; ++i)
el.parentNode.appendChild(r[r.length] = document.createElement("span")).appendChild(document.createTextNode(s.charAt(i)));

el.parentNode.removeChild(el);
} else {
// e has to be an array, otherwise it gets modified by getLetters() while we're using it.
for (var e = Array.prototype.slice.call(el ? el.childNodes : []), i = 0; i < e.length; ++i)
r.push.apply(r, getLetters(e[i], true));
if(arguments[1])
el.parentNode.appendChild(el);
}

return r;
}

// Generate a random colour.
function randomColour() {
return "#" + pad(Math.floor(Math.random() * 0x1000000), 6, 16);
}

// Get a random sample of an array.
function randomSample(arr, n) {
for (var r = [], i = n - 1, a = arr.slice(); a.length && i >= 0; --i)
r.push(a.splice(Math.floor(Math.random() * a.length), 1)[0]);
return r;
}

// Update all the letters.
function tick() {
for (var s = randomSample(c, Math.floor(coverage * c.length)), i = s.length - 1; i >= 0; --i)
s[i].style.color = randomColour();
}

// The function to be called on elements.
function rainbow(el) {
if (arguments.length > 1) {
for (var i = arguments.length - 1; i >= 0; --i)
rainbow(arguments[i]);
return t;
}

c.push.apply(c, getLetters(el));
if (!t)
t = setInterval(tick, interval);

return t;
}

return rainbow;
})({
interval: 100,
coverage: 0.2
});

onload = function() {
rainbow(document.getElementById("test"));
};
</script>
</head>
<body>
<p id="test">A <em>test</em> item.</p>
</body>
</html>

ChrisRickard
02-19-2008, 10:53 PM
Heh, the innerHTML I guess is a vestigial implementation detail of what I was originally going to do before supporting nested elements. I'm a big fan of using string building/innerHTML because it's so much faster than createElement/appendChild. Of course since I have to enumerate all the child elements of the temporary element and call insertBefore, this performance gain is negated.

But in the end I guess it goes to show our differing views of DHTML coding styles. I'll happily break a W3C recommendation for performance or ease of use as long as it's widely accepted enough. However it's not my intention to start a standards debate.

So I guess the question is where do we go from here? You don't want to put up any code or code example that breaks any W3C recommendation, a decision I can understand and respect. On the other hand I certainly can't lay any claim to your code, as it is a from scratch creation. Additionally I do like the way my version works and I'm unwilling to change it in such a radical fashion. This is something I hope you can understand as a developer.

Normally I would say we're at an impasse, with me respectfully withdrawing my submission. However there is the question of the old version from back in 2000. (Talk about a standards abomination!) The whole reason I did this was to create a replacement for it that worked in all the latest browsers while at the same time maintaining a "scriptless" declarative usage.

If nothing else could you please remove the outdated email address? I'm sure the mail admin at paccoast.com would appreciate it as would I :)

molendijk
02-19-2008, 11:41 PM
Twey, I agree that innerHTML is non-standard (although widely supported). But my personal and sad experience is that replacing it with standard DOM-methods often produces something that does not function in IE. Your script, for instance, fails in IE (6, probably in IE 7 too), whereas the ChrisRickard script works fine (in IE and in other browsers). As long as people use IE, we'll have to take that into consideration.

Arie Molendijk.

Twey
02-19-2008, 11:47 PM
'm a big fan of using string building/innerHTML because it's so much faster than createElement/appendChild.Currently. However, innerHTML performs a whole bunch of (usually unnecessary) operations more than DOM methods do, so it will probably end up being considerably slower.
I'll happily break a W3C recommendation for performance or ease of use as long as it's widely accepted enough. However it's not my intention to start a standards debate.It's not just standards it has going against it -- it also leads to very bad code. Code is not a string, and there are almost always better ways of going about coding than using strings to represent code. This applies to HTML as much as it does to Javascript (see eval(), for example). While there are some languages that are designed to be able to represent their code as data (mostly Lisps), and it's a very useful and elegant feature to have, strings are definitely not the way to go about it.
o I guess the question is where do we go from here? You don't want to put up any code or code example that breaks any W3C recommendationPlease be aware that John and myself are just members of this forum, and not associated with DD in any way. It's ddadmin's choice as to whether he updates your script or not, I was simply offering a suggestion as to how it could be done better.
But my personal and sad experience is that replacing it with standard DOM-methods often produces something that does not function in IE. Your script, for instance, fails in IE (6, probably in IE 7 too)In this case, since the script is fairly simple, I suspect it could be modified to work with IE -- I have to take considerable pains to test with IE, so I didn't for this, which is really just an example. In the (surprisingly few) cases where the DOM methods absolutely cannot be made to work in IE, innerHTML can be used as a fallback, thanks to the try/catch block. It shouldn't be the primary choice of coding style, however, much as is the case with, say, document.all or other non-standard features of legacy browsers.

molendijk
02-20-2008, 12:10 AM
In the (surprisingly few) cases where the DOM methods absolutely cannot be made to work in IE, innerHTML can be used as a fallback, thanks to the try/catch block. It shouldn't be the primary choice of coding style, however, much as is the case with, say, document.all or other non-standard features of legacy browsers.
I agree with that. It's just that, in my experience, there are more cases of IE-DOM-failure than you might suspect. There's only one solution: IE should be banned; this browser causes too much trouble all the time. But then again: it's there to stay.

Arie.

jscheuer1
02-20-2008, 04:53 AM
Geez, and I thought I was being hard on this guy! Lighten up folks! Or mmm, I don't mean to ruffle any feathers here either by implying that the mood was too heavy or anything.

Chris, you should know that, although this is the submit area, and that both Twey and I are experienced coders and moderators, we have no real say in whether your code gets accepted or not. That is up to ddadmin, who has yet to weigh in, and who may be favorably influenced in that he has accepted your code in the past, and that this is an update to it.

But ddadmin is busy, so may not get to this thread for awhile, feel free to PM him, just to say 'Hi', though please don't pester him.

And let me say that regardless of any coding differences we may have at the moment, I cut my coding teeth on the scripts here in the DD library. So it is in part thanks to folks like you, the early contributors, that I have gotten to where I am. Thank You.


- J

ChrisRickard
02-21-2008, 12:43 AM
Code is not a string, and there are almost always better ways of going about coding than using strings to represent code.
I have to disagree with you there. What is code but a set of strings that is parsed and compiled (or in this case interpreted) into a set of instructions? I believe this applies even more so to markup like HTML. Think about what it would be like if we had to make every piece of HTML an imperative command! HTML wasn't even conceived with the notion of a DOM, it really started as an add on hack that became wildly popular. This is one of the main reasons why I don't subscribe to a pedantic level of conformity to standards that attempt to make a perfect system out of a series of hacks to a product that was never intended for any of this craziness.


Please be aware that John and myself are just members of this forum, and not associated with DD in any way.
Thanks for the clarification, I made the assumption that as moderators to the submission forums you were also the gatekeepers.


I was simply offering a suggestion as to how it could be done better.
I know it may not seem like it but I do appreciate the suggestions. :D I had actually left the client side web development game back in 2004 just before the whole AJAX/Web 2.0 stuff took off and made DHTML cool again. As such much of my skills haven't aged well. Take for instance your suggestion of using closesures: The ability in JavaScript to use them has been around forever, but back then it wasn't really a common technique. It didn't have all the articles devoted to their uses like they do today. Now I know that it's fairly common practice and that I should brush up on how and when to use them. I thank you for that especially.


In this case, since the script is fairly simple, I suspect it could be modified to work with IE
In this case the reason it doesn't in IE work is because of an ambiguity in the way the ECMAScript 3 and DOM 2 are written.

From ECMAScript 3 reference to Array.splice()

The splice function is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method. Whether the splice function can be applied successfully to a host object is implementation-dependent.

And according to DOM 2 ECMAScript Language

The NodeList object has the following properties:

length
This read-only property is of type Number.

The NodeList object has the following methods:

item(index)
This method returns a Node object.
The index parameter is of type Number.
Note: This object can also be dereferenced using square bracket notation (e.g. obj[1]). Dereferencing with an integer index is equivalent to invoking the item method with that index.

So the ability to use splice on a NodeList is optional behavior that happens to be supported by most browsers.

ChrisRickard
02-21-2008, 12:52 AM
And let me say that regardless of any coding differences we may have at the moment, I cut my coding teeth on the scripts here in the DD library So it is in part thanks to folks like you, the early contributors, that I have gotten to where I am. Thank You.
Thanks, I really appreciate it. Again I would also like to thank you and Twey in turn for your constructive criticism and suggestions. Looking at your profiles I see that you both have a seemingly tireless devotion to this site that I commend.

Also I agree that the mood in this thread is a little heavy, which is mostly my fault for getting defensive. Sorry 'bout that. :(

jscheuer1
02-21-2008, 02:51 AM
Within limits (and I think we are within them here), unsupported objects or objects like slice whose allowed application varies can be created from supported objects for those browsers that require it, or a different approach altogether can be taken. It all depends upon how many of the properties of the unsupported or limited object are required by the code.

Twey
02-21-2008, 09:35 AM
What is code but a set of strings that is parsed and compiled (or in this case interpreted) into a set of instructions?Luckily for us, there's a parser to parse it from strings, so we don't have to worry about working with it as strings. What makes elegant homoiconism possible in Lisps is the fact that all code is represented as lists -- a Lisp program is its own parse tree. Treating HTML as strings is kind of like writing your own operating system so that you can write "hello world" -- it's just reinventing the wheel and working at a much lower level than is efficient (as we've seen here).
Think about what it would be like if we had to make every piece of HTML an imperative command!This doesn't necessarily follow -- just because it's parsed doesn't mean it has to be imperative. HTML could be described as a (very limited) declarative programming language.
HTML wasn't even conceived with the notion of a DOM, it really started as an add on hack that became wildly popular.And if that's all it still were today we wouldn't be able to do any of this. Thank goodness for the DOM :)
This is one of the main reasons why I don't subscribe to a pedantic level of conformity to standards that attempt to make a perfect system out of a series of hacks to a product that was never intended for any of this craziness.Unfortunately it seems we're stuck with it, so we might as well try to make it as nice as possible.
I know it may not seem like it but I do appreciate the suggestions.You're welcome :)
The ability in JavaScript to use them has been around forever, but back then it wasn't really a common technique.It still isn't, really, because most Javascript is still done by people who think it's a subset of Java :) Amongst serious JS users it's quite common, though, and I think it has been for quite a while.
In this case the reason it doesn't in IE work is because of an ambiguity in the way the ECMAScript 3 and DOM 2 are written.The only place I called splice() was here:
function randomSample(arr, n) {
for (var r = [], i = n - 1, a = arr.slice(); a.length && i >= 0; --i)
r.push(a.splice(Math.floor(Math.random() * a.length), 1)[0]);
return r;
}... and the only place this was called was here:
function tick() {
for (var s = randomSample(c, Math.floor(coverage * c.length)), i = s.length - 1; i >= 0; --i)
s[i].style.color = randomColour();
}... and c is definitely an array, because I initialised it as such here:
var t = null, // To store the interval ID.
c = [], // To store the letters to be updated.
interval = opts.interval || 100,
coverage = opts.coverage || 5;The two slice() calls are equally safe (although one was on a NodeList using call(), but this is supported cross-browser). I don't believe NodeLists have a splice() property in any browser.

jscheuer1
02-21-2008, 02:04 PM
function getLetters(el) {
var r = [];

if (el.nodeType === Node.TEXT_NODE) {
for (var s = el.nodeValue, i = 0; i < s.length; ++i)
el.parentNode.appendChild(r[r.length] = document.createElement("span")).appendChild(document.createTextNode(s.charAt(i)));

el.parentNode.removeChild(el);
} else {
// e has to be an array, otherwise it gets modified by getLetters() while we're using it.
for (var nL=el.childNodes, nLA=[], i = 0; i < nL.length; ++i)
nLA.push(nL[i]);
for (var e = Array.prototype.slice.call(nLA), i = 0; i < e.length; ++i)
r.push.apply(r, getLetters(e[i], true));
if(arguments[1])
el.parentNode.appendChild(el);
}

return r;
}

Twey
02-21-2008, 06:44 PM
I tell a lie, it does indeed seem to be an issue, I fixed it thus:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Testcase</title>
<script type="text/javascript">
if (typeof Node === "undefined")
var Node = {
TEXT_NODE: 3
};

// Apply an animated "rainbow" effect to an element, with random colours.
var rainbow = (function(opts) {
var t = null, // To store the interval ID.
c = [], // To store the letters to be updated.
interval = opts.interval || 100,
coverage = opts.coverage || 5;

// Zero-pad a number.
function pad(num, places, radix) {
var s = num.toString(radix || 10);
return (new Array(places - s.length + 1)).join('0') + s;
}

// Convert an arbitrary object to an array.
function toArray(o) {
if (!o) return [];

var r;

try {
// This is faster. Try it first.
r = Array.prototype.slice.call(o);
} catch (e) {
r = [];

for (var i = 0; i < o.length; ++i)
r.push(o[i]);
}

return r;
}

// Give each letter its own <span> and add it to an array of elements to update.
function getLetters(el) {
var r = [];

if (el.nodeType === Node.TEXT_NODE) {
for (var s = el.nodeValue, i = 0; i < s.length; ++i)
el.parentNode.appendChild(r[r.length] = document.createElement("span")).appendChild(document.createTextNode(s.charAt(i)));

el.parentNode.removeChild(el);
} else {
// e has to be an array, otherwise it gets modified by getLetters() while we're using it.
for (var e = toArray(el && el.childNodes), i = 0; i < e.length; ++i)
r.push.apply(r, getLetters(e[i], true));
if(arguments[1])
el.parentNode.appendChild(el);
}

return r;
}

// Generate a random colour.
function randomColour() {
return "#" + pad(Math.floor(Math.random() * 0x1000000), 6, 16);
}

// Get a random sample of an array.
function randomSample(arr, n) {
for (var r = [], i = n - 1, a = arr.slice(); a.length && i >= 0; --i)
r.push(a.splice(Math.floor(Math.random() * a.length), 1)[0]);
return r;
}

// Update all the letters.
function tick() {
for (var s = randomSample(c, Math.floor(coverage * c.length)), i = s.length - 1; i >= 0; --i)
s[i].style.color = randomColour();
}

// The function to be called on elements.
function rainbow(el) {
if (arguments.length > 1) {
for (var i = arguments.length - 1; i >= 0; --i)
rainbow(arguments[i]);
return t;
}

c.push.apply(c, getLetters(el));
if (!t)
t = setInterval(tick, interval);

return t;
}

return rainbow;
})({
interval: 100,
coverage: 0.2
});

onload = function() {
rainbow(document.getElementById("test"));
};
</script>
</head>
<body>
<p id="test">A <em>test</em> item.</p>
</body>
</html>Strange, I was sure that was cross-browser.

Sorry John, didn't see your post. This:
// e has to be an arrayshould have been satisfied by the slice() call. Apparently IE doesn't appreciate me passing it COM objects.

jscheuer1
02-21-2008, 07:02 PM
Hey, that's OK. I had noticed a similar thing in a script I've been working on to fade colored text. In the unit that allows stings to be faded in gradually, a section at a time by being in separate tags, I allow the array of elements to be passed as an argument, or just the id of the parent. Working with the:

getElementsByTagName

of that parent proved to be problematical in IE and/or maybe Opera when I wanted to overwrite an item from it as though it were an actual array. In that case, I realized I didn't need to, I could just get the value I wanted from the item, leaving it alone and in some cases adding an id attribute to it (it was already a tag, so that was no problem).