slayeroffice | how slayeroffice works
slayeroffice is a combination of Javascript, HTML and PHP and acts as more of a Javascript application than it does a standard web page. This document will serve to educate the interested visitor on how everything from the left menu navigation to the comments system to the content serving system work together to provide you, my faithful user, with the slayeroffice experience and to amaze you with what a complete javascript freak the author is.

I suspect that a good many traditionalists will be appalled at the way my site is set up and coded...its not exactly conventional. A lot of it is "hmm, i wonder how i could do this without doing it the most obvious way", as well as the fact that I do just about everything I possibly can on the client-side as I come from a school of thought that takes any effort it can to reduce server CPU load. Whatever excuse you'll accept, it's my site and I'll code it how i like. ;)

To facilitate this walk through, I will trace through the code as it is executed, begining with initPage() and going from there. initPage(), as you no doubt have guessed, is the very first method called (via the onLoad event of the BODY element) and is the page's equivalent to a java application's public static void main(). It is where things are, you guessed it -- initialized.

function initPage() {
	buildLinksArray();
	checkForParam();
	writeNews();
	stupidQuote();
	initCommentsArray();
}
The first method called from initPage() is buildLinksArray(). This method's purpose is to construct an array variable called linkArray and accomplishes this by looping over document.links.length and, when finding that a links className is subNavLink, adds it to the array. Links falling in that class are the ones that you will find in the expanding navigation menu. Click on arcade and all of the links that will appear are part of the subNavLink class. The array is declared within the global scope and serves two functions.
function buildLinksArray() {
	for(i=0;i<document.links.length;i++) {
		if(document.links[i].className == "subNavLink")linkArray[linkArray.length] = document.links[i].href;
	}
}
The first, and what is was designed for, is content page redirection. This might not make sense unless I first describe how content on slayeroffice is served up, so I'll do that now. I'll get into its second purpose later.

One of the most important elements on the main page is a 1x1 iframe called contentServer. If you look at the HTML source of the main page, you will see that every link in the navigation menu has its target attribute set to contentServer. When a user clicks on one of these links, for example the link to Alien Sequence in the arcade menu, the page loads in the contentServer iframe. The HTML source for a content page will look like this:

<script language="javascript" src="../../scripts/page_methods_v2.js"></script>
<body onload="sendContent();">
<div id="contentDiv" class="contentHTML">
	<b><font size=3>Alien Sequence</font></b>
	<hr noshade color="#00669" size=1>
	<b>Current Version:</b> 1.1<br>
	<b>Last Revision:</b> 12.01.02<br>
	<b>Language:</b> Javascript<br>
	<b>Requires:</b> Windows, Internet Explorer v5.5+<br>
	<img src="http://slayeroffice.com/gr/arcade_gr/alien_shot.gif" vspace=5><br>
	A rendition of the electronic handheld game "Lights Out" by Tiger.  40 levels of puzzles and sets a cookie so you dont have to start over from the begining each time you come back to play.
	<hr noshade color="#00669" size=1>
	<a target="_blank"  href="/arcade/alien_sequence/"><img src="/gr/go.gif" border=0></a> <a target="_blank"  href="../viewSource.php?f=arcade/alien_sequence/index.html&a=Alien Sequence"><img src="/gr/viewsource.gif" border=0></a> <a href="javascript:ocw()"><img src="/gr/comment.gif" border=0></a>
</body>
Note the sendContent() function being called from the onLoad event of the BODY element. What that function does is either call a function in the main page called serveContent() or redirect the user back to the main page if it discovers that the page has been loaded outside of the contentServer iframe.
function sendContent() {
	if(parent.document.location == document.location) {
		location.href="http://slayeroffice.com/index.php?c=" + location.href;
		return;
	} else {
		parent.serveContent();
	}
}
Assuming that the content page has been served up correctly, i.e. within the iframe, parent.serveContent() is called:
function serveContent() {
	if(gecko!=-1) {
		contentBlock = document.getElementById("contentServer").contentDocument.getElementById("contentDiv").innerHTML;
	} else {
		contentBlock = document.contentServer.document.getElementById("contentDiv").innerHTML;
	}
	document.getElementById("content").innerHTML = contentBlock;
	showComments();
}
It reads the innerHTML of the contentServer hosted page's contentDiv and sets it to the innerHTML of the content div on the main page. IE and Mozilla based browsers reference these objects differently, hence the conditional. This provides for a "transitionless" navigation that I like a lot, as well as a very easy to maintain set of documents. We'll come back to that call to showComments() in a bit.

What if the content page is served up outside of the confines of the contentServer iframe? That brings us back to the redirect and the first function of the linkArray variable. Why redirect them? The content pages are very, very ugly outside of the controls of the main page's style sheets - not to mention that there is no way to get back to the main page or to any other content from them, so they have no business being served up on their own.

So, when the content page is served up outside of contentServer, the user is redirected back to the main page. This could provide a frustrating experience for a user who followed a link from google to very briefly see the content they wanted only to be bounced away from it with no idea how to navigate back to it. Looking back at the sendContent() function, you'll see that a paramater called c is passed back to the main page, with a value of location.href.

This brings us to the third function called from initPage() -- checkForParam().

function checkForParam() {
	paramString = location.href;
	if(paramString.indexOf('?')>-1) {
		param = paramString.substring(paramString.indexOf('?')+3,paramString.length);
		for(i=0;i<linkArray.length;i++) {
			if(linkArray[i].indexOf(param)!=-1) {
				if(gecko==-1) {	
					document.contentServer.document.location.href = linkArray[i];
				} else {
					document.getElementById("contentServer").src = linkArray[i];
				}
			break;
			}
		}
	}
}
This function checks location.href for a ? which indicates GET data. If found it defines a variable named param as a substring of location.href... the value of c, which is the url of the content page. The function then loops over the length of linkArray, trying to find a match in the array for the value of c. If found, I send that value to contentServer, which in turn calls sendContent(), which then loads the page the user originally requested into the main page and the user sees the content they want in the context that I want them to see it in. Very handy and happens completely within the confines of the existing code to serve up pages, with the exception of the first step not being a click by the user.

But why store the links in linkArray you ask? Why not just load any old url thats passed to the main page into contentServer? Because not any old url needs to be -- for example, if linkArray wasnt in place and some smart ass decided to pass index.php to the main page, he would send the browser into an eternal loop as it loaded itself over and over and over again. Thats no good for my server and no good for that user's browser which would eventually crash.

Next in the initPage() line-up is writeNews():

function writeNews() {	
	closeLists();
	document.getElementById("mComments").innerHTML = "";
	mDoc = document.getElementById('content');
	mHTML = "";
	for(i=(news.length-1);i>=(news.length-3);i--)mHTML+= news[i] + "<hr noshade color=#006699 size=1>";
	mDoc.innerHTML = mHTML + "<p align=right><a class=\"newsLink\" href=\"javascript:writeAllNews();\">[archived news]</a></font>";
}
The first thing this function does is call closeLists():
function closeLists() {
	for(i=0;i<listNames.length;i++)document.getElementById(listNames[i]).style.display="none";
}
An array that contains all of the ID values of all of the navigation menu expansion lists (ex, arcade, tools, etc) is looped over and its corresponding element has its display set to none. The reason for this is that writeNews() is also the href for the main link in the menu, and part of the menu functionality is that when a top level menu item is clicked, any other open menu items will close. But more on that later.

writeNews() is a very simple function -- all it does is loop backwards over the length of an array called news until it has three news items, append each index's value to mHTML and then set the content div's(yes, the same div element that is used to display the content pages) innerHTML to the value of mHTML.

You'll notice the reference to writeAllNews() as an href wrapped around [archived news] which is written after the most recent three news blurbs.

function writeAllNews() {
	openWin("",300,300);	
	mHTML="<body bgcolor=#E8F8FF><title>slayer.office.archived_news</title><link rel='stylesheet' type='text/css'  href='scripts/styles_v2.css'>";	
	for(i=0;i<news.length;i++){
		mHTML += "<font face=arial color=#006699 style=\"font-size:8pt;text-decoration:none;\">" + news[i] + "<hr noshade color=#006699 size=1>";	
	}	
	w.focus();
	w.document.write(mHTML);
}
writeAllNews() does just what its name implies - it writes out every last bit of news that is available. The first thing it does is call openWin, a purely utilitarian function used across the site when in need of a custom sized, center screen window.
function openWin(url,wdth,hght) {
	t = (screen.height-hght)/2;
	l = (screen.width-wdth)/2;
	w=window.open(url,'cWin','nostatus,resizable=no,scrollbars=1,width='  + wdth + ',height=' + hght + ',top=' + t + ',left=' + l);
}
The function uses the window object created by openWin as a blank slate upon which to write out all of the news. No physical HTML document exists for archived news - it's all remains stored in a javascript array.

writeAllNews() loops over the entire length of the news array and appends each index's value to mHTML and does a little HTML formatting with it. When the loop completes, it sets focus to the blank window and then uses a document.write(mHTML) to create the content on the page.

So where does this news array come from? It comes from a PHP script that is referenced as a javascript src. news.php looks like this:

<?header("Content-Type: text/javascript");?>

news = new Array();
<?php
$rFile = fopen("news.txt","r");
$contents = fread ($rFile, filesize("news.txt"));
fclose($rFile);
$contents = split(chr(10),$contents);

for($i=0;$i<count($contents);$i++) {
	$tmp = split('~',$contents[$i]);
	$nDate = $tmp[0];
	$nContent = $tmp[1];
	$nContent = str_replace('\'','\\\'',$nContent);
	echo("news[" . $i . "]='<b>" . $nDate . ":</b> " . $nContent . "';" . chr(13));
}

?>
What this does is read in a file called news.txt, split the file's contents on line returns, loop over the length of that resulting array while further splitting each index in that array by tildes, giving us the date of the news in the 0th index and the news text in the 1st index. An example of what a line from news.txt looks like:
06.23.03~Added <a href="http://slayeroffice.com/content/code/swapNode.html" class="newsLink" target="contentServer">swapNode</a> to the code section as an example of how to rearrange objects in the DOM by using cloneNode, insertBefore and removeChild.
The script then formats the data from the text file as a javascript array and echo's it out to the page, making the data stored in the text file available to us as javascript.

Why not just keep the news updates as javascript variables in something like news.js? Well, I used to do it that way -- but then I decided to do an RSS feed for the site, which would mean that I would have needed to update two seperate files with identical information. I am much too lazy for this sort of thing, so news.txt is used by /rss/index.php as well. One formats the text into javascript, the other into XML.

/rss/index.php:

<?header("Content-Type: text/xml");?>
<?php echo('<?xml version="1.0" encoding="iso-8859-1"?>');?>
<rss version="0.91"> 
<channel> 
<title>slayeroffice.com</title> 
<link>http://slayeroffice.com</link> 
<description /> 
<language>en-us</language> 
<webMaster>steve@slayeroffice.com</webMaster> 
<?php
$rFile = fopen("../scripts/news.txt","r");
$contents = fread ($rFile, filesize("../scripts/news.txt"));
fclose($rFile);
$contents = split(chr(10),$contents);

for($i=count($contents)-1;$i>count($contents)-10;$i--) {
	$tmp = split('~',$contents[$i]);
	$newsDate = $tmp[0];
	$newsContent = $tmp[1];
	$newsContent = str_replace('>','&gt;',$newsContent);
	$newsContent = str_replace('<','&lt;',$newsContent);
	echo('<item>');
	echo('<title>' . $newsDate . '</title>'); 
	echo('<description>');
	echo(str_replace('\\','',$newsContent));
	echo('</description>');
	echo('<link>http://slayeroffice.com</link>');
	echo('</item>');
}
?>
</channel>
</rss>
After writeNews() finishes doing its thing, stupidQuote() is called. This is an incredibly simple function that simply selects a random number between zero and the length of an array that contains all of the quotes, and then sets it as the innerHTML of the div in the upper right corner of the main page.
function stupidQuote() {	
	document.getElementById('mQuote').innerHTML = quote[Math.floor(Math.random() * quote.length)];
}
Now we come to initCommentsArray(), the last function called from initPage(). This function also happens to be the secondary purpose of linkArray().
function initCommentsArray() {
	for(i=linkArray.length-1;i>-1;i--) {
		x = linkArray[i];
		x = x.replace(/http:\/\/slayeroffice.com\/content\//,"");
		x = x.split("/");
		x[1] = x[1].replace(/\.html/,"");
		if(!comments[x[0]])comments[x[0]] = new Array();
		if(!comments[x[0]][x[1]])comments[x[0]][x[1]] = new Array();
	}
	initComments();
}
As you can see, it loops backwards over the length of linkArray -- why backwards? no reason, honestly. I suppose for(i=0...) just gets boring after a while.

The function sets the value of each index in linkArray to a variable called x. It then strips out http://slayeroffice.com/content/ from the value and splits what remains on a forward slash. Finally it removes the .html from the 1st index of the newly created array.

To what end? Ah, this is coming to the good part now. The value of each index in x is to be used as primary and secondary index's in an array called comments. As you are sure to note, the very next line checks for the existence of comments[x[0]] and defines it as a new Array(), and then does the same for its subindex.

For the faint of heart, here is what this breaks down into:

This leaves us with several very empty multi-dimensional arrays. Now to populate those arrays as needed with initComments();
function initComments() {
	<? include 'comments.txt'; echo(chr(10));?>
}
More PHP trickery!

Now is probably the best time to explain how the comment system works. You've probably guessed that much like the news, the comments arent actually javascript. If this is what you've guessed I am sorry to inform you that you are incorrect, especially given that you have read so far into this now. I feel as though I have lead you astray and for this I apologize. The comments, as they are stored, are javascript arrays. Very deep arrays, at that. Here is an example of what a comment array looks like:

comments["arcade"]["snake"][comments["arcade"]["snake"].length] = new Array("test","08.6.2003@11:59 AM","steve");
Since there is no easy or realistic way for us to know how large that particular dimension of the comments array is at this point, we simply use its own length property as the newest index in the array.

These arrays are stored in comments.txt, the file included via php in initComments(). How do they get there? The first step, obviously, is the HTML page that hosts the form in which the user enters their comment:

<html>
<title>slayeroffice | add a comment</title>
<style type="text/css">
@import:url("http://slayeroffice.com/scripts/styles_v2.css");
</style>
<script language="javascript" type="text/javascript">
if(!window.opener)location.href = "http://slayeroffice.com";
function initHiddenFields() {
	zIndex = window.opener.getCommentIndex();
	document.forms.frm.arrayIndex.value = zIndex[0];
	document.forms.frm.arraySubIndex.value=zIndex[1];
}
</script>
</head>

<body bgcolor="#E8F7FD" onLoad="initHiddenFields();">

<div id="content">
<form name="frm" method="POST" action="/scripts/addComment.php">
Your name:<input type="text" class="textField" size=30 name="userName"><br>
Your comment:<br>
<textarea class="textField" rows=8 cols=46 name="userComment"></textarea><br>
<input type="hidden" name="arrayIndex"><input type="hidden" name="arraySubIndex">
<input type="submit" value="Add comment" class="btn">
</form>
</div>
</body>
</html> 
The only thing of true note in this HTML is the initHiddenFields() function which is called from the onLoad event of the body element. It calls getCommentIndex() from the window.opener object, i.e. the main page.
function getCommentIndex() {
	url = document.all?document.contentServer.document.location.href:document.getElementById("contentServer").contentDocument.location.href;
	if(url.indexOf("http")==-1 || url.indexOf("contact.html")!= -1 || url.indexOf("links")!=-1 || url.indexOf("about.html")!=-1)return 0;
	url = url.replace(/http:\/\/slayeroffice.com\/content\//,"");
	url = url.split("/");
	url[1] = url[1].replace(/\.html/,"");
	return url;
}
getCommentIndex() sets a variable called url to the current location of everyone's favorite iframe, contentServer. As stated earlier, IE handles this differently than Gecko/Mozilla based browsers so we have to conditionalize how we get that url based on DOM support.

Once I have url, I check to make sure its value is something I want to deal with. If it doesn't contain "http" or does contain "contact.html", "links" or "about.html" I have the function return 0. On a return of 0, I know that I do not have something that a comment can be made on, so I disallow it.

If the value of url passes this condition, I then do to it what I do to the values of linkArray in initCommentsArray(), that is, strip it of everything we dont want and split it into two values. The function then returns to the calling function those two values. In the case of the example made for initCommentsArray(), these values would be arcade and alien.

Returning to the comment entry page and initHiddenFields() -- the return of getCommentIndex() is then set as the value of two hidden fields in the form, arrayIndex and subArrayIndex. The user does their thing, hits the submit button and the form POSTs to addComment.php.

<html>
<head>
<title>slayeroffice | comment added</title>
<style type="text/css">
@import:url("http://slayeroffice.com/scripts/styles_v2.css");
</style>
<script language="javascript">
setTimeout("window.close()",3000);
</script>
</head>
<?
$arrayIndex =  $_POST["arrayIndex"];
$arraySubIndex =  $_POST["arraySubIndex"];
$userName = $_POST["userName"];
$userName = htmlspecialchars($userName);
$userName = trim($userName);
$userName = stripslashes($userName);
$userComment = $_POST["userComment"];
$userComment = str_replace(chr(10),'',$userComment);
$userComment = str_replace(chr(13),'',$userComment);
$userComment = htmlspecialchars($userComment);
$userComment = trim($userComment);
$userComment = stripslashes($userComment);
$rDate = date("m.j.Y@h:i A");

if($arrayIndex != "" || $arraySubIndex != "" || $userComment != "") {
	if ($userName == "") $userName = "anonymous";
	$js = chr(10) . "comments[\"" . $arrayIndex . "\"][\"" . $arraySubIndex . "\"][comments[\"" . $arrayIndex . "\"][\"" . $arraySubIndex . "\"].length] = new Array(\"" . $userComment . "\",\"" . $rDate . "\",\"" . $userName . "\");";
	$rFile = fopen("comments.txt","a");
	fwrite($rFile,$js,strlen($js));
	fclose($rFile);
}
?>

<body bgcolor="#E8F7FD">
<div id="content">
Thanks for adding your comment. Reload slayeroffice to see it.<p>This window will close in a second or two.
</div>
</body>
The PHP script grabs all the POSTed data, strips it of white space, HTML and other special characters to prevent people from doing anything sneaky or weird, sets the current date and time to a variable and then formats the javascript array. It then opens comments.txt for writing with the a parameter, which tells it to "append". The data is written out to the file, the file handle is closed, and we now have a brand new, shiny comment in the comments.txt file.

So now that you know how the comments are stored, you might be wondering how they are served. Observe:

function showComments() {
	document.getElementById("mComments").innerHTML = "";
	commentIndex = getCommentIndex();
	if(commentIndex == 0)return;
	if(comments[commentIndex[0]][commentIndex[1]] && comments[commentIndex[0]][commentIndex[1]].length) {
		mHTML = "<b>Comments:</b><p>";
		for(i=0;i<comments[commentIndex[0]][commentIndex[1]].length;i++) {
			mDate = comments[commentIndex[0]][commentIndex[1]][i][1];
			mComm = comments[commentIndex[0]][commentIndex[1]][i][0];
			mName = comments[commentIndex[0]][commentIndex[1]][i][2];
			mHTML+= "<b>" + mDate + "</b> - " + mComm + "<br>" + mName + "<p>";
		}
		document.getElementById("mComments").innerHTML = mHTML;
	return;
	} 
	document.getElementById("mComments").innerHTML = "<b>No comments</b>. Click the comment button above to add one.";
}
You will most likely recall that showComments() is called from serveContent(), the function that displays the content in contentServer in the content div element on the main page.

The first thing we do in showComments() is clean up the div that comments are written to, mComments. We then make a quick call to our good friend getCommentIndex(), explained just moments ago. We first check to make sure that the array in question exists AND that it has a non-zero length. If this condition is not met, we simply set the innerHTML of mComment to "No Comments", otherwise we set up our HTML to display the comments.

Looping over the length of the comment array, we access each sub-sub-index (yes, sub-sub) of the secondary index and set its value to a temporary variable. Array's as array index's are always hard to read, so here's how that would break down using the previous example of arcade and alien.

Where i is the increment variable in the for loop, and 1 is the index of the date in the comment array, just as 0 and 2 are the comment and user's name, respectively.

The values of these arrays are appended, as always, to mHTML and then set as the innerHTML of mComments.

The last remaining function is el, which is short for "expand list". Its a very simple function that simply change the display attribute of the top level menu elements from none to block.

function el(objID) {
	if(openMenu != objID) {
		closeLists();
		openMenu = objID;
		document.getElementById(objID).style.display="block";
		document.title = "slayer.office | " + objID
	} else {
		closeLists();
		openMenu="objID";
		document.getElementById(objID).style.display="none";
		document.title = "slayer.office";
	}
}
This function is called via the href's of the aforementioned navigation elements, which pass to it their ID's. For example, clicking on "arcade" passes just that. openMenu is a global variable that keeps track of which menu is currently open. If the passed in value isnt openMenu, the function closes the currently open menu, otherwise it opens it. It also sets the document title, so that when you are browsing the arcade listing, the title reflects that.

Well, that about covers it. The inner workings of slayeroffice.com -- congratulations if you made it all the way down here, I'm proud of you. I really didnt think I could make it this far and I wrote this damned thing. Hopefully you were able to get some useful information from this rambling, or maybe it gave you some ideas for how to build your site. If you have any questions about anything I covered in this document or have an idea to improve any of it, feel free to send a mail to steve@slayeroffice.com.


How slayeroffice works
Last revision: 08.07.2003
steve@slayeroffice.com
http://www.slayeroffice.com