/*
USEFUL FUNCTIONS
JS1.01 - addLoadEvent
JS1.02 - $

HELPER
JS2.01 - helper.clear
JS2.02 - helper.setOpacity
JS2.03 - helper.getElementsByClassName
JS2.04 - helper.importXml
JS2.05 - helper.preloadImages
JS2.06 - helper.getChild
JS2.07 - helper.createElement

EFFECT
JS3.01 - effect.fade
JS3.02 - effect.toggle
JS3.03 - effect.blankScreen [CSS]
JS3.04 - effect.highlight

CROSS-BROWSER COMPATABILITY
JS4.01 - helper.ieVersion
JS4.02 - helper.scrollOffset
JS4.03 - helper.windowHeight

STRING EXTENDERS
JS5.01 - String.checkClass

ARRAY EXTENDERS
JS6.01 - Array.createList









NOTE: Make sure that this JS file is included before any others on the page.
NOTE: Items in the index above tagged with [CSS] require css/jsf.css to be included
TIP:  When a function requires an object, you can pass it a string containing the object's ID instead if you wish.

DEV NOTES
- helper.importXml() based on code from http://www.webdeveloper.com/forum/showthread.php?threadid=147548
- helper.ieVersion() based on code from http://www.thefutureoftheweb.com/blog/detect-ie6-in-javascript
*/









/* --- INITIALISATION --- */

jsfEffect = function() {}
jsfHelper = function() {}
jsfVars = function() {}

// do not modify
jsfVars.prototype.version = "1.1";
jsfVars.prototype.fading = false;

addLoadEvent(frameworkInit);

function frameworkInit()
{
  jsf = new jsfVars();
  effect = new jsfEffect();
  helper = new jsfHelper();
} // frameworkInit()










/* --- JS1.00 - USEFUL FUNCTIONS --- */

// JS1.01
// addLoadEvent
// queues load events, and executes them on page load one after the other

function addLoadEvent(func)
{
  var oldonload = window.onload;
  if (typeof window.onload != 'function')
    window.onload = func;
  else
  {
    window.onload = function()
    {
      if (oldonload) oldonload();
      func();
    } // function()
  } // else
} // addLoadEvent()

// JS1.02
// shortcut for document.getElementById
// returns an array if passed multiple strings

function $()
{
  switch(arguments.length)
  {
    case 0:
      return false;
      break;
    
    case 1:
      return document.getElementById(arguments[0]);
      break;
    
    default:
      var objectArray = new Array();
      for (var i=0; i < arguments.length; i++)
      {
        objectArray.push(document.getElementById(arguments[i]));
      } // for
      return objectArray;
      break;
  } // switch
} // $()









/* --- HELPER --- */

// JS2.01
// deletes all child nodes of an object

jsfHelper.prototype.clear = function(element)
{
  var obj = (typeof element == "object") ? element : document.getElementById(element);
  while(obj.firstChild) obj.removeChild(obj.firstChild);
} // jsfHelper.clear()

// JS2.02
// sets the opacity of an element
// value can be from 0 to 100

jsfHelper.prototype.setOpacity = function(element, value)
{
  var obj = (typeof element == "object") ? element : document.getElementById(element);
	obj.style.opacity = (value == 0) ? 0 : value / 100;
	obj.style.filter = 'alpha(opacity=' + value + ')';
} // jsfHelper.setOpacity()

// JS2.03
// takes a start element, an element to match, and a class to check
// returns an array of elements
// eg. helper.getElementsByClassName("navigation", "li", "active")
//     this would return all list items with a class of "active" who are children of #navigation

jsfHelper.prototype.getElementsByClassName = function(startElement, elementToMatch, classToCheck)
{
  var objArray = new Array();
  var obj = typeof startElement == "object" ? startElement : document.getElementById(startElement); 
  var elementArray = obj.getElementsByTagName(elementToMatch);
  for (var i = 0; i < elementArray.length; i++)
  {
    if (classToCheck.checkClass(elementArray[i])) objArray.push(elementArray[i]);
  } // for
  return objArray;
} // jsfHelper.getElementsByClassName()

// JS2.04
// imports an XML document
// returns a document; if there is an error then it a returns a string describing the error

jsfHelper.prototype.importXml = function(fileName)
{
	try
	{
		if (window.ActiveXObject)
		{
			var error = "Check Browser and security settings";
			xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
			xmlDoc.async = false;
			xmlDoc.load(fileName);
		} // if
		else if(window.XMLHttpRequest)
		{
			var error = "Error handling XMLHttpRequest request";
			var d = new XMLHttpRequest();
			d.open("GET", fileName, false);
			d.send(null);
			xmlDoc = d.responseXML;
		} // else if
		else
		{
			var error = "Unknown error";
			xmlDoc = document.implementation.createDocument("","",null);
			xmlDoc.async = false;
			xmlDoc.load(fileName);
		} // else
		return xmlDoc;
	} // try
	catch(e)
	{
		return error;
	} // catch
} // jsfHelper.importXml()

// JS2.05
// preloads images
// the first argument is the directory containing the images
// the remaning arguments (however many you want) are the images to be preloaded

jsfHelper.prototype.preloadImages = function(directory)
{
  preloadedImages = new Array();
  for (var i = 1; i < arguments.length; i++)
  {
    preloadedImages[i] = new Image();
    preloadedImages[i].src = directory + arguments[i];
  } // for
} // jsfHelper.preloadImages()

// JS2.06
// returns the first child element of an element that isn't a text node (can be passed either a string or an object)
// used for cross-browser compatability due to IE not including whitespace as a text node


jsfHelper.prototype.getChild = function(element, settings)
{
  // settings
  var nextSibling = false; // return the next sibling instead of the first child
  var prevSibling = false; // return the previous sibling instead of the first child
  
  if (settings != undefined)
  {
    var arrSettings = settings.split(", ");
    for (var i = 0; i < arrSettings.length; i++)
    {
      var setting = arrSettings[i].split(": ")[0];
      var value = arrSettings[i].split(": ")[1];
      if (setting == "return" && value == "nextSibling") nextSibling = true;
      else if (setting == "return" && value == "prevSibling") prevSibling = true;
    } // for
  } // if
  
  var parentElement = (typeof element == "object") ? element : document.getElementById(element);
  
  if (!nextSibling && !prevSibling) // get first child
  {
    for (var i=0; i < parentElement.childNodes.length; i++)
    {
      if (parentElement.childNodes[i].nodeType == 1)
      {
        return parentElement.childNodes[i];
      } // if
    } // for
  } // if
  else if (nextSibling)// get next sibling
  {
    var eleNextSibling = parentElement.nextSibling;
    if (!eleNextSibling || !eleNextSibling.nodeType) return false; // break out if no sibling found
    
    while (eleNextSibling.nodeType != 1)
    { 
      eleNextSibling = eleNextSibling.nextSibling; 
      if (!eleNextSibling || !eleNextSibling.nodeType) return false; // break out if no sibling found
    }
    
    return eleNextSibling;
  } // else
  else if (prevSibling)
  {
    var elePrevSibling = parentElement.nextSibling;
    if (!elePrevSibling || !elePrevSibling.nodeType) return false; // break out if no sibling found
    
    while (elePrevSibling.nodeType != 1)
    {
      elePrevSibling = elePrevSibling.previousSibling;
      if (!elePrevSibling || !elePrevSibling.nodeType) return false; // break out if no sibling found
    }
    return elePrevSibling;
  } // else
  
  return false;
} // jsfHelper.getChild()

// JS2.07
// returns the specified element populated with a text node
// settings can be specified to add attributes (id, class, href, rel, title)

jsfHelper.prototype.createElement = function(element, text, settings)
{
  var ele = document.createElement(element);
  var txt = document.createTextNode(text);
  ele.appendChild(txt);
  
  // settings
  if (settings != undefined)
  {
    var arrSettings = settings.split(", ");
    for (var i = 0; i < arrSettings.length; i++)
    {
      var setting = arrSettings[i].split(": ")[0];
      var value = arrSettings[i].split(": ")[1];
      
      if (setting == "id") ele.id = value;
      if (setting == "class") ele.className = value;
      if (setting == "href") ele.href = value;
      if (setting == "rel") ele.rel = value;
      if (setting == "title") ele.title = value;
    } // for
  } // if
  
  return ele;
} // jsfHelper.createElement()









/* --- EFFECT --- */

// JS3.01
// fades an element in or out
//
//            SETTINGS
//               fade: (str)   [in], out - whether to fade an element in or out
// allowMultipleFades: (bool)  [false]   - whether or not to allow multiple items to be faded at once
//            seconds: (float) [1]       - time in seconds for fade to complete
//             target: (int)   [var]     - target opacity (default 100 if fading in, 0 if fading out)
//                                         if setting a target when fading in, set the same target when fading out for compatability reasons (it will still fade to 0)

jsfEffect.prototype.fade = function(element, settings)
{
  var fade = "in";
  var allowMultipleFades = false;
  var seconds = 1;
  var target = 100; // target opacity
  
  if (settings != undefined)
  {
    var arrSettings = settings.split(", ");
    for (var i = 0; i < arrSettings.length; i++)
    {
      var setting = arrSettings[i].split(": ")[0];
      var value = arrSettings[i].split(": ")[1];
      
      if (setting == "fade" && value == "out") fade = "out";
      if (setting == "allowMultipleFades") allowMultipleFades = (value == "true") ? true : false;
      if (setting == "seconds") seconds = parseFloat(value);
      if (setting == "target") target = parseInt(value);
    } // for
  } // if
  
  if (fade == "out")
  {
    originalTarget = target;
    target = 0;
  } // if
  
  if (allowMultipleFades || !jsf.fading)
  {
    jsf.fading = true;
    var obj = (typeof element == "object") ? element : document.getElementById(element);
    var milliseconds = seconds * 1000; // millseconds to complete fade
    
    if (obj.style.filter == "alpha(opacity=0)") helper.setOpacity(obj, 0);
    else
    {
      if (fade == "in")
      {
        if (obj.style.opacity == "" || obj.style.filter == "") helper.setOpacity(obj, 0);
      } // if
      else
      {
        if (obj.style.opacity == "" || obj.style.opacity == "0" || obj.style.filter == "") helper.setOpacity(obj, originalTarget);
      } // else
    } // else
    
    var currentOpacity = obj.style.opacity * 100;
    
    var rangeVal;
    if (fade == "in") rangeVal = milliseconds / (target - currentOpacity) * 100;
    if (fade == "out") rangeVal = milliseconds / (currentOpacity - target) * 100;
    
    var increment = 1; // amount to increase opacity by per cycle
    if (rangeVal < 3000) increment = 2;
    if (rangeVal < 2000) increment = 5;
    if (rangeVal < 1000) increment = 10;
    
    var delay = milliseconds/((target - currentOpacity)/increment);
    if (fade == "out") delay = delay *- 1; // reverse negative delay value
    obj.style.display = "";
    effect.fadeCycle(obj, fade, delay, increment, target);
  } // if
} // jsfEffect.fade()

jsfEffect.prototype.fadeCycle = function(obj, fade, delay, increment, target)
{
  var opacity = obj.style.opacity * 100;
  var fadeFinished = false;
  
  if (fade == "in")
  {
    if (opacity < target)
    {
      helper.setOpacity(obj, opacity + increment);
      setTimeout(function() {effect.fadeCycle(obj, fade, delay, increment, target)}, delay);
    } // if
    else fadeFinished = true;
  } // if
  else
  {
    if (opacity > target)
    {
      helper.setOpacity(obj, opacity - increment);
      setTimeout(function() {effect.fadeCycle(obj, fade, delay, increment, target)}, delay);
    } // if
    else fadeFinished = true;
  } // else
  
  if (fadeFinished)
  {
    helper.setOpacity(obj, target);
    jsf.fading = false;
    if (fade == "out") obj.style.display = "none";
  } // if
} // jsfEffect.prototype.fadeCycle()

// JS3.02
// fades an element in or out depending on whether or not it is visible
// settings are passed to effect.fade (obviously you don't specify the 'fade' setting, this is determined by effect.toggle)

jsfEffect.prototype.toggle = function(element, settings)
{
  var obj = (typeof element == "object") ? element : document.getElementById(element);
  if (settings == undefined) settings = "";
  if (obj.style.display == "none") effect.fade(obj, settings);
  else effect.fade(obj, 'fade: out, ' + settings);
} // jsfEffect.toggle()

// JS3.03
// blanks the screen
//
// SETTINGS
//    clear: (bool) [false] - removes the effect
//     fade: (bool) [false] - fades the effect in and out if true, otherwise blanking is instant
//  seconds: (int)  [0.5]   - if fading, time in seconds taken to fade

jsfEffect.prototype.blankScreen = function(settings)
{
  var clear = false;
  var fade = false;
  var seconds = 0.5;
  
  if (settings != undefined)
  {
    var arrSettings = settings.split(", ");
    for (var i = 0; i < arrSettings.length; i++)
    {
      var setting = arrSettings[i].split(": ")[0];
      var value = arrSettings[i].split(": ")[1];
      
      if (setting == "clear") clear = value;
      if (setting == "fade") fade = value;
      if (setting == "seconds") seconds = value;
    } // for
  } // if
  
  if (!clear)
  {
    // blank the screen
    var dimmer = document.createElement("div");
    dimmer.id = "dimmer";
    dimmer.style.display = "none";
    
    if (helper.ieVersion() == "6")
    {
      dimmer.style.width = document.documentElement.clientWidth;
      dimmer.style.height = document.body.scrollHeight;
      dimmer.style.position = "absolute";
    } // if
    
    document.body.className += " dimmed";
    document.body.appendChild(dimmer);
    if (fade) effect.fade(dimmer, "seconds: " + seconds + ", target: 20");
    else dimmer.style.display = "";
  } // if
  else
  {
    // clear the screen
    if (fade)
    {
      effect.fade($('dimmer'), "fade: out, seconds: " + seconds + ", target: 20");
      setTimeout("document.body.removeChild($('dimmer'))", 1000);
      setTimeout("document.body.className = document.body.className.replace(' dimmed', '')", (seconds * 1000) + 250);
    } // if
    else
    {
      document.body.removeChild($('dimmer'));
      document.body.className = document.body.className.replace(" dimmed", "");
    } // else
  } // else
} // jsfEffect.blankScreen()

// JS3.04
// highlights an element for a number of seconds
// typically used to highlight new content inserted via AJAX

jsfEffect.prototype.highlight = function(element, settings, cycle)
{
  element = (typeof element == "object") ? element : document.getElementById(element);
  if (cycle == undefined) cycle = 0;
  
  // default settings
  var colours = [ '#ffffaa', '#ffffbb', '#ffffcc', '#ffffdd', '#ffffee', '#ffffff' ];
  var initialDelay = 800;
  var delay = 80;
  
  // custom settings
  if (settings == undefined) settings = "";
  if (settings != "")
  {
    var arrSettings = settings.split(", ");
    for (var i = 0; i < arrSettings.length; i++)
    {
      var setting = arrSettings[i].split(": ")[0];
      var value = arrSettings[i].split(": ")[1];
      
      if (setting == "initialDelay") initialDelay = parseInt(value);
      if (setting == "delay") delay = parseInt(value);
    } // for
  } // else
  
  element.style.backgroundColor = colours[cycle];
  cycle++;
  if (cycle == 1) setTimeout(function(){effect.highlight(element, settings, cycle)}, initialDelay);
  else if (cycle < colours.length) setTimeout(function(){effect.highlight(element, settings, cycle)}, delay);
} // jsfEffect.highlight()









/* --- CROSS-BROWSER COMPATABILITY -- */

// JS4.01
// returns version of IE, or -1 for other browsers

jsfHelper.prototype.ieVersion = function()
{
  return (navigator.appName == 'Microsoft Internet Explorer') ? parseFloat((new RegExp("MSIE ([0-9]{1,}[.0-9]{0,})")).exec(navigator.userAgent)[1]) : -1;
} // jsfHelper.ieVersion()

// JS4.02
// returns the scroll offset of the browser window
// accounts for cross-browser compatability

jsfHelper.prototype.scrollOffset = function()
{
  var scrollOffset1 = document.documentElement.scrollTop;
  var scrollOffset2 = window.pageYOffset;
  if (scrollOffset1 == undefined) scrollOffset1 = 0;
  if (scrollOffset2 == undefined) scrollOffset2 = 0;
  var scrollOffset = (scrollOffset1 == 0) ? scrollOffset2 : scrollOffset1;
  return scrollOffset;
} // jsfHelper.scrollOffset()

// JS4.03
// returns the height of the browser window
// accounts for cross-browser compatability

jsfHelper.prototype.windowHeight = function()
{
  var windowHeight1 = document.documentElement.clientHeight;
  var windowHeight2 = window.innerHeight;
  if (windowHeight1 == undefined || windowHeight1 == 20) windowHeight1 = 0;
  if (windowHeight2 == undefined) windowHeight2 = 0;
  
  // correct Opera's changed windowHeight handling as of version 9.27
  if (windowHeight1 > 0 && windowHeight2 > 0)
  {
    if (windowHeight1 > windowHeight2) windowHeight1 = 0;
    else windowHeight2 = 0;
  } // if
  
  var windowHeight = (windowHeight1 == 0) ? windowHeight2 : windowHeight1;
  return windowHeight;
} // jsfHelper.windowHeight()









/* --- STRING EXTENDERS --- */

// JS5.01
// checks if the string appears in the class of an object, or another string
// returns true or false

String.prototype.checkClass = function(element)
{
  var className = (typeof element == "object") ? element.className : element;
  var classArray = className.split(" ");
  for (var i=0; i < classArray.length; i++)
  {
    if (this == classArray[i])
    {
      return true;
    } // if
  } // for
  return false;
} // String.checkClass()









/* --- ARRAY EXTENDERS --- */

// JS6.01
// takes an array and turns it into an unordered list
// returns a DOM object

Array.prototype.createList = function()
{
  var listObj = document.createElement("ul");
  for (var i = 0; i < this.length; i++)
  {
    var listItem = document.createElement("li");
    listItem.appendChild(document.createTextNode(this[i]));
    listObj.appendChild(listItem);
  } // for
  return listObj;
} // Array.createList()
