/*
Copyright © Tribal Education Ltd. 

This file has been compressed to make the page loading faster. For working version of this file, please contact Tribal's Digital Learning Studio at digitallearningsupport@tribalgroup.com.
*/

/**
*   functions.js is a toolbag of useful functions
*
*	most js files need functions.js and should call 
*	ctad_require("functions.js") at the top
*	functions.js itself requires settings.js
*
*	this file is for generic code onl;y: no magic numbers or html writing.
*	legacy functions are in legacy.js, menu functions are in menu.js
*
*   compatible: ns6+, ie4+ 
*	@author: geoff stead
*	@author: lee meadows
*	@author: jon halle
*	@author Ivars Veksins
*	$Id: functions.js,v 2.21 2004/12/15 16:33:11 jonh Exp $
*/


//###############################################################
// code which runs when functions.js is included.
//###############################################################

var CTAD_INCLUDED = new Array(); //declare global array for includes

// first check for settings.js. this does not use require structure
// as its not available when setting.js runs
if (typeof(CTAD_SETTINGS)=="undefined"){
	ctad_error("settings.js was not included before functions.js");
}else{
	ctad_included("settings.js");
}

//and of course this one is included too
ctad_included("functions.js");

//now install browser extensions
var ctad_extend= new Extend();

ctad_extend.getElementById();
ctad_extend.getElementsByTagName();
ctad_extend.string_slice();
ctad_extend.array_pop();
ctad_extend.array_contains();



//###############################################################
// require functionality
//###############################################################

/**
make sure the specified js file is included
similar to functionality in other languages, except cannot actually include the file
if you use document.write "<scr"+"ipt src='my.js'></scr"+"ipt>"); it will be included 
_after_ the current code completes

@argument js_file_required name of js file that the calling code needs ie myjs.js
@argument js_file_asking name of js file that is requiring. optional (only used for error display)
*/
function ctad_require (js_file_required,js_file_asking){

	if (typeof(CTAD_INCLUDED[js_file_required])=="undefined"){
		if (!js_file_asking){
			js_file_asking="unidentifed javascript file";
		}
		ctad_error(js_file_asking+" requires the javascript file "+js_file_required);
	}

}

/**
register a js file as being included. complement to ctad_require

@argument js_file_name name of js file ie myjs.js
*/
function ctad_included (js_file_name){
	
	//first check to see if it is already included
	
	if (typeof(CTAD_INCLUDED[js_file_name])!="undefined"){
		ctad_error(js_file_name+" already included");
	}
	CTAD_INCLUDED[js_file_name]=true;
}

//#########################################################################
// error handling
//#########################################################################

/**
display an error if debug mode on

@argument error_string description of error
*/
function ctad_error(error_string){
	if (CTAD_SETTINGS["functions_debug"]){
		alert("Developer Error\n"+error_string);
	}
}

/**
display an error even if debug mode off (ie to end user)
probably of limited use

@argument error_string description of error
*/
function ctad_error_user(error_string){
		alert("Sorry, an error occurred\n"+error_string);
}

//#########################################################################
// browser extensions class
//#########################################################################

/**
Class containing extensions to and fixes for browser bugs and incompatibilities
*/
function Extend(){
//all methods are within constructor

	/**
	IE4 and netscape don't have getElementById so try to install a replacement
	not using prototype because ie4 doesnt have document.protoype
	*/
	Extend.prototype.getElementById=function(){
		if (!document.getElementById){
			if (document.all){
				document.getElementById=function(id){
					return(document.all[id]);
				};
			}else{
				document.getElementById=function(id){
					alert("Sorry, this browser is not supported");
					return;
				};
			}
		}
	};

	/**
	IE4 and netscape don't have getElementsByTagName so try to install a replacement
	not using prototype because ie4 doesnt have document.protoype
	*/
	Extend.prototype.getElementsByTagName=function(){
		if (!document.getElementsByTagName){
			if (document.all){
				document.getElementsByTagName=function(tag_name){
					return(document.all.tags[tag_name]);
				};
			}else{
				document.getElementsByTagName=function(id){
					alert("Sorry, this browser is not supported");
					return;
				};
			}
		}
	};

	/**
	repair String.slice in browsers where its broken (does not accept negative start)
	kudos to TIBET project for this approach and mostly the code
	*/
	Extend.prototype.string_slice=function(){
		if ('abcde'.slice(-3,-2).length == 1){
			//if it aint broke don't fix it
			return;
		}
		
		//  copy the reference to the existing implementation to a new slot
		String.prototype.$$slice = String.prototype.slice;
			/**
				same functionality as documented slice
			*/
		    String.prototype.slice = function(start, end){

		        if (start < 0){ //ie4 has a bug with negative indexes
		            //  choose either 0 or the valid index
		            start = Math.max(0, this.length + start);
		        }
				
				//gotta have something in end param
				if (!end){
					end=this.length;
				}
		        //  pass on to the original version with the indexes recomputed
		        return this.$$slice(start, end);
		    };
	};

	/**
	extend the Array object to add a 'pop' method if it doesnt already have one
	IE up to 5.5 does not have Array.pop()
	*/
	Extend.prototype.array_pop=function(){

		if (Array.prototype.pop){
			//if it aint broke don't fix it
			return;
		}
		
			/**
				same functionality as documented pop
				returns last element in Array
				removes that element from the array
			*/
			Array.prototype.pop=function(){
				if (this.length<1){
					return;
				}
				var value=this[this.length-1];
				this.length=this.length-1;
				return(value);
			};
	};

	/**
	extend the Array object to add a 'contains' method
	*/
	Extend.prototype.array_contains=function(){
		/**
		test if array contains a value
		@argument value value to test for in array
		
		@return true if array contains value, otherwise false
		*/
		Array.prototype.contains = function(value){
			for (var i=0;i<this.length;i++){
				if (this[i]==value){
					return(true);
				}
			}
			
			return(false);
		};
	};
	
}

//#########################################################################
// general utilities and extensions
//#########################################################################

/**
Use this function to test for an empty value.
*/
function MT(value) {
	return (value == null) || (value == "");
}


/**
helper function for inheritance
dont extend the native Object object
or it ends up confusing, 
ie when iterating with for/in you get extra properties

called as first thing in constructor
inherit_object (this,new MamaClass());

@argument sub_class class which is inheriting
@argument super_class class to inherit from
*/
function inherit_object (sub_class,super_class) { 

       for (prop in super_class) { 
               sub_class[prop] = super_class[prop]; 
       } 
}

//#####################################
// stylesheet functions
//#####################################

/**
find whether a stylesheet has already specified an attribute
remember to try likely selectors ie .null_anchor and a.null_anchor

@argument selector what selector to look for in stylesheet ie 'a' or '.myclass'
@argument style_attribute what attribute to look for ie 'color'
@return true if attribute set in stylesheet otherwise false
*/
function style_in_stylesheet(selector,style_attribute){

	if (!document.styleSheets){
		return (false);
	}
	
	for (var ss = 0; ss < document.styleSheets.length; ss++){
	     var tss = document.styleSheets[ss];

	
		if (!tss.cssRules){
			tss.cssRules = tss.rules;
		}
		
	    for (var ru = 0; ru < tss.cssRules.length; ru++){

			if (typeof(tss.cssRules[ru].selectorText) != "undefined"){
				_match = new RegExp(selector,"gi"); 
				if (tss.cssRules[ru].selectorText.match(_match)) { 
					if (typeof(tss.cssRules[ru].style[style_attribute])!="undefined"){
						return (true);
					}	
				}				
			}		
		}
	}
	return(false);
}

/**
event chaining function 

allows arbitrary functions to be assigned to an event without disturbing existing functions assigned to that event
all libraries should use this function to asssign functions to  events. 

where naive code would say:
<pre> myobject.onclick=myfunc;</pre>
more responsible code would say:
<pre> event_install(myobject,"onclick",myfunc);</pre>

this function respects functions which have been assigned inline in html as well as functions which have been assigned in code
it also preserves the 'this' keyword

note that there is no guarantee as to the order in which functions will be called. IE attachEvent documentation say the
functions will be called in 'random order'.

@arg obj object which event is to be installed on
@arg event_name string containing name of event ie "onclick"
@arg f reference to function to be installed (ie without brackets, not in quotes)
*/
function event_install(obj,event_name,f){
	if (obj.addEventListener){	//W3C
		obj.addEventListener(event_name.substr(2), f,false);
	}else if (obj.attachEvent){	 //IE 
		obj.attachEvent(event_name, function(){obj._f=f;obj._f();obj_f=null});
	}
}


//#####################################
// cookie functions
//#####################################

/**
detect if cookies are available
*/
function cookies_available(){

	var use_cookies=false;

	//use browser defined value if available
	if (typeof navigator.cookieEnabled=="undefined"){ 
		if (!document.cookie){
			//try a test cookie
			document.cookie="testcookie";
			use_cookies=(document.cookie=="testcookie");
			
			//erase dummy value
			document.cookie=""; 
		}else{
			//if there is a cookie then cookies work
			use_cookies=true;
		}
	}else{
		use_cookies =navigator.cookieEnabled;
	}

	return (use_cookies);

}

/**
determine whether to use cookies based on availability and 
persist_force_frameset setting
*/
function cookies_use(){
	return (cookies_available() && !CTAD_SETTINGS["persist_force_frameset"]);
}


/**
Cookie class from rhino book doctored by jh to use cookies more efficiently
a single key/value pairs string is created	which gets split up into cookie-sized chunks
All the predefined properties of this object begin with '$'
to distinguish them from other properties which are the values to
be stored in the cookie.

@argument document:		Required Document object value.
@argument prefix:		Required string which all the cookies made by this object 
				will start with.
@argument days:			Optional number that specifies the days from now
				that the cookie should expire.
@argument path:			Optional string that specifies the cookie path attribute.
@argument domain:		Optional string that specifies the cookie domain attribute.
@argument secure:		Optional Boolean value, if true, requests a secure cookie.
@argument max_size:		Optional max size of cookie
@argument max_cookies:	Optional max number of cookies

*/
function Cookie(document, prefix, days, path, domain, secure, max_size, max_cookies) {


  this.$document = document;
  this.$prefix = prefix;
	if (days){
		this.$expiration =  new Date((new Date()).getTime() + days * 86400000);
	}else{
		this.$expiration = null;
	}
  if (path){   this.$path   = path;   }else{ this.$path   = null;}
  if (domain){ this.$domain = domain; }else{ this.$domain = null;}
  if (secure){ this.$secure = true;   }else{ this.$secure = false;}
  if (max_size){ this.$max_size = max_size;   }else{ this.$max_size = 4000;}
  if (max_cookies){ this.$max_cookies = max_cookies;   }else{ this.$max_cookies = 20;}

//now doctor the max size based on the other stuff we are puting into each cookie
	
	var metadata= this.metadata();
	this.$max_size-=metadata.length;

}

/** 
Store method of cookie object

Loop through the properties of the Cookie object and
put together an array of cookie strings.

then create the cookie(s)
*/
Cookie.prototype.store=function() {  

this.$ar_cookie_strings=Array(); 
this.$cookie_index=0;


  for(var prop in this) {  // Ignore "$" properties, and methods
		if ((prop.charAt(0) == '$') || ((typeof this[prop]) == 'function')) {
		    continue;
		}

		var pair=prop + ":" + escape(this[prop]);
		this.fill_cookie(pair);
 
   }
   

//for each string in the array we create a new cookie
	
	for (var i=0;i<this.$cookie_index+1;i++){
		this.create_one(this.$prefix+i,this.$ar_cookie_strings[i]);
	}

};

/**
Recursive function to fill up cookies as much as possible and then move on to next one.
creates $ar_cookie_strings
@argument pair key-value pair
*/
Cookie.prototype.fill_cookie=function(pair){


	//initialise array if needed
	if (!this.$ar_cookie_strings[this.$cookie_index]){
		this.$ar_cookie_strings[this.$cookie_index]="";
	}
	
	var current_cookie_length=this.$ar_cookie_strings[this.$cookie_index].length;
	
	//set up pair with ampersand if needed
	if (current_cookie_length!=0){
		pair="&"+pair;
	}
	
	// check to see that the key-value pair is not too long
		if (pair.length>this.$max_size){
			ctad_error('cookie: string not saved, it was too long: ' + pair.length + '/' + this.$max_size + ' bytes');
			return;
		}
		
	//if cookie prop and val would push us into the next cookie
       if ((current_cookie_length+pair.length)>this.$max_size){
		
			//check to see that we aren't out of cookies
			if (this.$cookie_index+2>this.$max_cookies){
				ctad_error('cookie: string not saved, we ran out of cookies');
				return;
			}
			
			//try the next cookie
			this.$cookie_index++;
			this.fill_cookie(pair);
		
     }else{
	
			//otherwise now add to the cookie array
			this.$ar_cookie_strings[this.$cookie_index]+=pair;
	}
};

/**
returns the cookie metadata
*/
Cookie.prototype.metadata=function(){
	var metadata = "";

	if (this.$expiration)
      metadata += '; expires=' + this.$expiration.toGMTString();
	if (this.$path) metadata += '; path=' + this.$path;
	if (this.$domain) metadata += '; domain=' + this.$domain;
	if (this.$secure) metadata += '; secure';
  
	return(metadata);
};

/**
creates one cookie
@argument name name of the cookie to make
@argument val value to store
*/
Cookie.prototype.create_one=function (name,val){

	var cookie = name + '=' + val;
	cookie+=this.metadata();

  this.$document.cookie = cookie;  // store with magic property
};

/**
get a list of all cookies that pertain to this document.
We do this by reading the magic Document.cookie property. 
*/
Cookie.prototype.load=function () {  


var allcookies = this.$document.cookie;
  if (allcookies == "") return false;
	
var ar_cookies_all = allcookies.split(/;\s*/);
var cookieval="";
var i=0;
var ar_pairs = Array();
var ar_crumb = Array();

for(i = 0; i < ar_cookies_all.length; i++) {
	var ar_cookie = ar_cookies_all[i].split("=");
	if (ar_cookie[0].substring(0,this.$prefix.length)==this.$prefix){
		cookieval=ar_cookie[1];
		ar_crumb=cookieval.split("&");
		ar_pairs=ar_pairs.concat(ar_crumb);
	}
}


/* Now that we've parsed the cookie value, set all the names and values
of the state variables in this Cookie object. Note that we unescape()
the property value, because we called escape() when we stored it.
*/
  for(var i = 0; i < ar_pairs.length; i++) {
	var ar_pair=ar_pairs[i].split(":");
    this[ar_pair[0]] = unescape(ar_pair[1]);
  }

  // We're done, so return the success code.
  return true;
};

/**
remove a cookie
*/
Cookie.prototype.remove =function () {  
var cookie;
  cookie = this.$name + '=';
  if (this.$path) cookie += '; path=' + this.$path;
  if (this.$domain) cookie += '; domain=' + this.$domain;
  cookie += '; expires=Fri, 02-Jan-1970 00:00:00 GMT';

  this.$document.cookie = cookie;  // magic store
};

//#####################################
// general window fun mostly from dreamweaver
//#####################################

var win= null;
function newwindow(mypage,myname,w,h,scroll,top){
	
	var winl = (screen.width-w)/2;
	if (typeof(top)!="undefined"){
		var wint = (screen.height-h)*top;
	}else{
		var wint = (screen.height-h)/2;
	}

	settings='height='+h+',width='+w+',top='+wint+',left='+winl+',scrollbars='+scroll+',toolbar=no,location=no,status=no,menubar=no,resizable=yes,dependent=no'
	//scrollbars disabled so that 5.5 works correctly
	win=window.open(mypage,myname,settings);
	if(parseInt(navigator.appVersion) >= 4){win.window.focus();}
	
	//return false;
}

/**
newwindowfocus checks first if the window has been already opened
used when opening the calculator
**/
function newwindowfocus(mypage,myname,w,h,scroll){
if ((win) && (win.open) && (!win.closed)) {
win.focus();
}
else {
var winl = (screen.width-w)/2;
var wint = (screen.height-h)/2;
settings='height='+h+',width='+w+',top='+wint+',left='+winl+',scrollbars='+scroll+',toolbar=no,location=no,status=no,menubar=no,resizable=yes,dependent=no'
win=window.open(mypage,myname,settings)
if(parseInt(navigator.appVersion) >= 4){win.window.focus();}
}
}

function popUp(URL) {
	
/* var winleft = (screen.width - 800) / 2;
var winUp = (screen.height - 600) / 2;
winleft=0;winUp=0; */ 

day = new Date();
id = "Win2";
var settings="toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=1,width="+(screen.width-13)+",height="+(screen.height-65)+",left=0,top=0";
eval("page" + id + " = window.open(URL, '" + id + "', '"+settings+"');");

}
function writeTestletFolder(sco) {
	var whichFolder = "";
	switch(sco) {
		case "hot_topics_01":
			whichFolder = "l_1_t_3"; //verbs and tenses
			break;
		case "hot_topics_02":
			whichFolder = "l_2_t_3"; //format and structure
			break;
		case "hot_topics_03":
			whichFolder = "n_2_t_3"; //tables charts and graphs
			break;
		case "hot_topics_04":
			whichFolder = "n_1_t_1";
			break;
		case "hot_topics_05":
			whichFolder = "l_1_t_2";
			break;
	}
	return whichFolder;
}

function LM_pagefix(thenum){
if(thenum < 10){
	thenum = "0" + thenum;
}
return thenum;
}