Friday, April 18, 2008

Coding Logic

This blog entry was prompted by a script used by the Shuttle and my response in the original column pointing to it.

It seems a simple task, click a link and have some information appear above, click another link and have it change. Easy to do if you want one page for every link, but wouldn't it be easier if all the information was loaded up onto one page and you just changed the text?

This is how the Shuttle saw it, provide all the biographies of candidates in <div> containers, hide them, and then display the appropriate one when the link is clicked; but how to do it?

Hiding the biography containers is simplicity itself as CSS provides a style called display which can be set to "none" doing so means that your browser ignores that bit and just pretends it's not there. When you want to see it just set the display style to "block" and it appears as if by magic.

Okay some technical bits, in order to manipulate the right biography (element) you need to give it a unique identity. This is done by using id="somename", then you tell the browser to use that identity by calling document.getElementById("somename"). Once you've done that you can mess with that element to your heart's content.

So to show the element "bio1" you use this scrap of javascript

document.getElementById("bio1").style.display = "block"

Everytime you click on a link it uses that same bit of code but with its own relevant identity; seems easy, but it's never that easy. The problem here is that when you can see "bio1" and then try to see "bio0" the former is sitting on top of the latter; they stack and hide each other. So every time you display one biography you need to hide the other, but because the viewer can click the links in any order they want to you don't know which one to hide.

It is at this point straightforward logic is applied, because I don't know which biography to hide I'll hide all of them each time. First up I need to know the identities of all the biography elements, then I hide them, then I show the one that was clicked. Because I'll be using this code multiple times I'll wrap it up into a function and I'll pass on the identity of which biography I want to display.

So in my link I use onclick="showBio('0')" which calls the function showBio and passes a '0' to it -

function showBio(bionum)
{

the '0' that was passed on is now stored as a variable called "bionum" the { tells the browser where the function starts

var divSrcArray = new Array('bio0','bio1','bio2')

Here I've created a new variable type, an array, called "divSrcArray". Arrays hold multiple pieces of information in separate 'boxes'; in this case the identies of three biography elements. Due to a computing 'quirk' arrays count from 0 upwards therefore the first 'box' is at position 0.

for (var i=0; i<divSrcArray.length; i++)
{

A little more complicated here, this is a for loop. What that does is tell the browser to keep doing something until certain conditions change, the something being contained in the { brackets. So first I'm creating another variable called "i" and making it equal to 0, I'm telling the loop to continue while "i" is less then how many pieces of information are in my array, in this case 3, then after the code in the loop has been done I want it to add 1 to "i". The code I want it to do in this loop is

document.getElementById(divSrcArray[i]).style.display = "none";
}

From above you should know that the "getElementById" is used to 'grab' the right element and that the last part hides it. The bit in the middle refers to one of the 'boxes' in that array I created at the beginning of the function. So the first pass through the loop when "i"=0 it's pulling the information out of 'box' 0, that is the first one and contains "bio0". With the for loop it'll keep doing that for 'box' 1 and 2 before it runs out of 'boxes' and leaves the loop. At which point is does this

document.getElementById("bio" + bionum).style.display = "block";
}

Again another 'grab' but this time to show the biography. In the middle I'm welding the word bio to the number that got passed to the function when it was called and was stored in the variable "bionum" in this case a 0. Then the final } bracket ends the function and that's it.

To recap - list all the biography identitys in an array called divSrcArray, loop through a bit of code that reads those identities and hides them, then finally show the biography that I wanted to see in the first place. This is the way the Shuttle does it.

On its own that's fine and it works, but imagine that instead of just the three biographys listed here you have three hundred or even three thousand. With this code it has to pull up every biography element and hide it despite the fact that only one of those three hundred is not already hidden. Not only that you have to create an array containing the identities of all three hundred biographies; not fun. To put it in geeky terms the code does not scale well; it works fine with small numbers, but start pushing it and cracks appear. So what can be done?

There are two items we can logically change. First of all as all the information stored in the array is sequential we don't need to store it separately so we can ditch the array and set the for loop with the number of biographies (if we still start at 0) and then use the same trick we did in welding the word "bio" to a number, in this case "i". Secondly we can also read in the current state of each biography and only change those that are being shown. This changes the function to:

function showgal(bionum)
{
for (var i=0; i<3; i++)
{
if (document.getElementById("bio"+i).style.display="block")
{
document.getElementById("bio"+i).style.display="none";
}
}
document.getElementById("bio" + bionum).style.display = "block";
}

The new bit here is the comparison operator "if", like the for loop it's quite simply - 'grab' the biography element's condition directly and see if it's set to "block", if so change it to "none", if not don't do anything and start the loop again. In terms of differences in time between this and the original function instead of writing to every biography element we're just reading and comparing and then only writing to the one that needs to be changed. So is that the end, no and this is the good bit, instead of modifying our first bit of code let's start again from the beginning.

One of the problems we saw when we first examined the situation was that we couldn't know which biography element to hide, the user could click on the links in any order they liked, however that's not a problem if we use a global variable. I won't get into details but some coders just don't like using global variables, however in this case it's justified. So what we do is create a variable outside the function, this means it only gets created once, and as know that on loading the first displayed biography is bio0 we can set it to 0 like so

var hide="0";

then start our function and use "hide" to manipulate the biography element we know is being displayed and then show the biography that was clicked.

function showBio(bionum)
{
document.getElementById("bio" + hide).style.display = "none";
document.getElementById("bio" + bionum).style.display = "block";

Finally as we now know which biography is being shown we set "hide" to the value of "bionum" like so and finish the function.

hide=bionum;
}

So in this function we're not storing an array of identities and we're not stepping through every biography element, we're just jumping to and changing the only elements we need to.

So why I am I writing this entry? Well it's purely to demonstrate how coders sometimes apply logic. The first function that was written was perfectly fine for the task it was asked to do; and I hope you followed the logic that created it. However when the numbers it was dealing with got to much for it, the first instinct was to 'fix' the code that was already present whittling down non-essentials in an effort to speed things up. The final function, I hope, shows that sometimes it's best to ignore what's already written and go back to the original task and ask "What was I trying to do again?".

0 comments: