/**
 * 
 *    Copyright (C) 2006 New Zealand Digital Library, http://www.nzdl.org
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *    
 */

try{ console.log('LLDL.js console.log function existence testing'); }catch(e) {console = { log: function() {} }; }
LLDL.services ={
     /***********************general services ******************/
     LOGJSERROR_SERVICE  : 0,
     GET_MESSAGE_SERVICE : 3,
     ADD_EXERCISE : 4,
     ADD_CATEGORY : 5,
     RENAME_EXERCISE : 6,
     RENAME_CATEGORY : 7,
     DEL_EXERCISE : 8,
     DEL_CATEGORY : 9,
     DEL_ALL_EXERCISES : 10,
     LIST_ALL_EXERCISES : 11,
     LIST_ALL_CATEGORIES : 12,
     DEL_ALL_CATEGORY : 13,

	 DESIGN_INTERFACE : 14,
	 DESIGN_QUERY : 15,
	 DESIGN_PREVIEW : 16,

     ACTIVITY_SERVICE  : 99,

     LOAD_ACTIVITY  : 100,
     SCOREBACK_SERVICE : 101,
     GROUP_LOGIN: 102,
     GROUP_LOGOUT:103,
	 CHAT_SERVICE : 104,//for group mode activities
	 SCORE_SERVICE : 105,//for group mode activities 
     RETRIEVE_MESSAGE : 106, //for group mode activities
     KEEP_ALIVE_SERVICE: 107,
     QUERY_USER_LIST: 108,//ImageGuessing (for now)
     RELOGIN: 109,
     
     GENERATE_EXERCISE_SERVICE : 1000,   
     BUILD_COLL : 1001,   
     SAVE_COLL : 1002,
     DEL_COLL : 1003,   
//   --NOT IN USE-- : 1004,
     QUERY_COLL_BUILD_STATE  : 1005,
     RETRIEVE_DOC : 1006,
     COPY_COLL:1007,
     SET_COLL_CATEGORY: 1008,
     LIST_COLL: 1009,
     MODULE_SITE_REGISTER: 1010,
	 DEL_MEDIA: 1011,
	 PAIR_UP: 1012,//ImageGuessing
	 USER_REPORT: 1013,//ImageGuessing
	 TEST_GLOSS: 1014,//build collection
   COLLO_PHRASE_FREQUENCY: 1015//CollocationAlternatives
};
var lls = LLDL.services;

LLDL.activities ={
       HEADER_TEXT : "",

	DEBUG : false,
    LOGIN_SERVICE : "Login",
    REGISTER_SERVICE: "Register",
    LOGOUT_SERVICE : "Logout",
    CHAT_SERVICE : "Chat",
    GET_MESSAGE_SERVICE: "GetMessage",
    USER_NAME_ATTR : "username",
    PASSWORD_ATTR: "password",	
    MESSAGE_ATTR : "message",
    TYPE: "type",
    ERROR_REASON_ATTR : "reasonoferror",//present when ERROR_MESSAGE inccurs
    ERROR_MESSAGE: -1,
    SUCCESS_MESSAGE: 100,
    
    getHeader:function(heading){
		var header = document.createElement("div");
		header.id = "header";
                this.HEADER_TEXT = heading;
	        header.innerHTML = "<h2 id='header-text'>" + heading +"</h2>";	
	        return header;
	},
             getInstruction:function(instr){
		var instruction = document.createElement("div");
		instruction.className = "activityinstruction";
	        instruction.innerHTML = instr;	
	        return instruction;
	    },

	    getFooter:function(footer_text){
               var footer = document.createElement("div");
               footer.id = "footer"; 
	       footer.appendChild(document.createTextNode(footer_text));	
	       return footer;
	    }, 

            getContent:function(){
	     var content = document.createElement("div");
	     content.id = "content";	
	     return content;
	    },

            getNavigation:function(){
	     var nav = document.createElement("ul");
	     nav.id = "nav";	
	     return nav;
	    },   


      updateHeader:function(text){
          var header = YAHOO.util.Dom.get("header-text");
          header.firstChild.nodeValue = this.HEADER_TEXT +text;

     }         
}  
var lldl = LLDL.activities;

//////////////////////shaoqun//////////////////////////////////////
LLDL.activities.ActivityButton = function(obj) {
    var btn = LLDL.createEl("input");
    btn.type = "button";
    btn.className = 'activitybutton';
    btn.value = obj.button_text;
    if(obj.fn && obj.context) {
        yue.on(btn,'click', obj.fn, obj.context, true);
    } else {
    	LLDL.setButtonDisable(btn, true);
    }       
    return btn;
};
LLDL.activities.How2Play = function(text, url, options, bReplace){
    this.body = LLDL.createEl("input","how2play");
    this.body.type ="button";   
    this.body.className = "activitybutton";
    this.body.value = "How to play";
    this.body.text = text;
    this.body.url = null;
    if(url){
       this.body.url = url;        
    }
    this.body.options = options || '';
    YAHOO.util.Event.addListener('how2play','click', this.show);

}
LLDL.activities.How2Play.prototype = {
  show: function(e){
    var target = yud.get("how2play");
    var wobj = null;  
    if(target.url){
      wobj = LLDL.openpopup(target.url, "HowToPlay", target.options);
    }    	
    else{
      wobj = LLDL.openpopup("", "HowToPlay", target.options);
    }
    wobj.document.write('<html><head><title>How to play</title>');
    wobj.document.write('<link rel="stylesheet" href="interfaces/flax/style/howtoplay.css?i=0"/>');
    wobj.document.write('</head><body>');
    wobj.document.write('<div class="how2play">' +
                          '<div class="how2play_header">How to play</div>' +
                          '<div class="how2play_body">' + target.text + '</div>' +
                        '</div>');
    wobj.document.write('</body></html>');
    wobj.document.close();
   }

}

LLDL.activities.ChatButton = function(id, chat_button_value){
    var el_id = id || '';
    var body = LLDL.createEl("input");
    body.id = el_id;
    body.type ="button";   
    body.className = "activitybutton";
    body.value = chat_button_value;
    return body;
}
LLDL.activities.ChatWindow = function(content){
    var wobj = LLDL.openpopup("", "chat");
    wobj.document.write('<html><head><title>Chat to others</title>');
    wobj.document.write('<link rel="stylesheet" href="interfaces/flax/style/chat.css?i=0"/>');
    wobj.document.write('</head><body>');
    wobj.document.write('<div class="chat">' +
                          '<div id="chat_header_id" class="chat_header">Chat to Others</div>' +
                          content +
                        '</div>');
    wobj.document.write('</body></html>');
    wobj.document.close();//flush the buffer to the pop window
    return wobj;
}
LLDL.activities.SummaryReportButton = function() {
    var body = LLDL.createEl("input");
    body.type ="button";   
    body.className = "activitybutton";
    body.value = "Summary report";
    return body;
}
LLDL.activities.SummaryReportWindow = function(report_summary, full_report_content) {
	var options = 'width=650,height=500';
    var wobj = LLDL.openpopup("", "summaryreport", options);
    wobj.document.write('<html><head><title>Summary Report</title>');
    wobj.document.write('<link rel="stylesheet" href="interfaces/flax/style/summaryreport.css?i=0"/>');
    wobj.document.write('</head><body>');
    wobj.document.write('<div class="report">' +
                          '<div class="report_header">Summary</div>' +
                          report_summary +
                          '<div class="report_header">Full Report</div>' +
                          full_report_content +
                        '</div>');
    wobj.document.write('</body></html>');
//    this.wobj.document.close();
    return wobj;
}
LLDL.activities.getPopupWindowItemName = function(text, tag_name) {
	var v = text || '';
	var tag = tag_name || 'li';
	return "<"+tag+" class='item_name'>" + v + "</"+tag+">";
}
LLDL.activities.getPopupWindowItemValue = function(text, tag_name) {
    var v = text || '';
    var tag = tag_name || 'li';
    return "<"+tag+" class='item_value'>" + v + "</"+tag+">";
}
LLDL.activities.getActivityContentContainer = function(id) {
	var el_id = id || '';
	return LLDL.createEl('div', el_id, '', 'activity_content');
}
LLDL.activities.getCommonActivityButtonsContainer = function(id) {
    var el_id = id || '';
    return LLDL.createEl("div", el_id, "", "common_activity_buttons");
}
LLDL.activities.getActivityStatsContainer = function(id) {
    var el_id = id || '';
    return LLDL.createEl("div", "", "", "activity_stats_panel");
}
LLDL.activities.getActivityDocumentTitleContainer = function(id, title_text) {
	var el_id = id || '';
	return LLDL.createEl("div", el_id, title_text, 'document_title');
}
LLDL.activities.getActivityDocumentContentBody = function(id) {
    var el_id = id || '';
    return LLDL.createEl('div', el_id, '', 'document_content');
}
///////////////////////////////////////////////////////////



LLDL.activities.Login = function(activity) {	      
   this.body = document.createElement("ul");
   this.body.activity = activity; 
   this.body.id = "loginview";
  }

LLDL.activities.Login.prototype = {

   loginButton: function(){
      var button = document.createElement("div");
      button.id = "loginbuttonview";
      button.appendChild(document.createTextNode(LLDL.bundle.activity_login)); 
      button.activity = this.body.activity ;    
      return button;
    },
   
   loginForm:function(){
      this.body.innerHTML ="<li>"+LLDL.bundle.activity_username+"<input id='username' type='text' /></li><li><button id='loginbutton'>"+LLDL.bundle.activity_login+"</button></li>";
      YAHOO.util.Event.addListener('loginbutton','click', this.login);
        LLDL.addKeyListener(this.body, 13, this.login, this);
//      YAHOO.util.Event.addListener(window,'keydown', this.login); 
    },

    
   login:function(e){
//	var target = YAHOO.util.Event.getTarget(e);
//        if (target.id.toLowerCase() != 'loginbutton' && target.id.toLowerCase() != 'username'){alert(target.id); return;}
/*

	 if (target.id.toLowerCase() == 'username'){
                       
             var keynum = "";
	    if(window.event) // IE
	      {
	       keynum = e.keyCode;
	      }
	    else if(e.which) // Netscape/Firefox/Opera
	     {
	      keynum = e.which;
	     }
          
         //we only interested in the ENTER key 
           if (keynum != 13) return;                     
       }
*/


        if (YAHOO.util.Dom.get("username").value == ""){
          alert(LLDL.activities.activity_username_warning);
          return; 
        }

        var activity = YAHOO.util.Dom.get("loginview").activity;
	var  url = "?a=pr&o=xml&rt=r&ro=1&s="+activity.activityName+"&s1.service="+LLDL.activities.LOGIN_SERVICE+"&c="+activity.collName;
      
        var request_type = "POST";
        var postdata = "&s1."+activity.USER_NAME_ATTR+"="+ YAHOO.util.Dom.get("username").value;
        postdata += "&s1."+activity.COLLECTION_NAME_ATTR+"="+activity.collName;
        postdata += "&s1."+activity.LEVEL_ATTR+"="+activity.level;
        if (activity.loginExtra){ 
             postdata += activity.loginExtra;
         }	  

        //alert(postdata);                                     
	YAHOO.util.Connect.asyncRequest(request_type , url , activity.loginCallback, postdata);
   }

}


LLDL.activities.Chat = function(activity) {
   this.body =  document.createElement("ul"); 
   this.body.id = "chatview";
   this.body.activity = activity;   
   this.body.innerHTML = "<li><textarea id='chattextarea' readonly='true' title='"+LLDL.bundle.activity_list_messages+"'>"+LLDL.bundle.chat_instruction+"</textarea></li><li><input id='chatenter' type='text' title=\""+LLDL.bundle.activity_type_message+"\"></input><span id='chatsend'>"+LLDL.bundle.activity_send+"</span></li>";
   YAHOO.util.Event.addListener('chatsend','click', this.sendmessage, this);
   LLDL.addKeyListener(this.body, 13,  this.sendmessage, this); 
   
}


LLDL.activities.Chat.prototype = {
   clear: function(){
        if (YAHOO.util.Dom.get("chattextarea")){
           YAHOO.util.Dom.get("chattextarea").value = "";
          }
       if (YAHOO.util.Dom.get("chatenter")){
        YAHOO.util.Dom.get("chatenter").value = "";
       }
   },

   sendmessage:function(e){
/*        var target = e.target;
        if (target.id.toLowerCase() != 'chatsend' && target.id.toLowerCase() != 'chatenter' ){return;}
  
        if (target.id.toLowerCase() == 'chatenter'){
            if (YAHOO.util.Dom.get("chatenter").value == "" ) return;           
           
             var keynum = "";
	    if(window.event) // IE
	      {
	       keynum = e.keyCode;
	      }
	    else if(e.which) // Netscape/Firefox/Opera
	     {
	      keynum = e.which;
	     }
          
         //we only interested in the ENTER key 
           if (keynum != 13) return;                     
       }
*/      
        var activity = YAHOO.util.Dom.get("chatview").activity;
	var  url = "?a=pr&o=xml&rt=r&ro=1&s="+activity.activityName+"&s1.service="+LLDL.activities.CHAT_SERVICE+"&c="+activity.collName;
	var request_type = "POST";
        var postdata = "&s1."+activity.NAME_ATTR+"="+activity.uname;
        postdata += "&s1."+activity.MESSAGE_ATTR+"="+YAHOO.util.Dom.get("chatenter").value;
        if (activity.pid != ""){
           postdata += "&s1."+activity.PARTNER_ID_ATTR+"="+activity.pid;
        } 
                              
	// send the message to yourself;
        var textarea = YAHOO.util.Dom.get("chattextarea");
	var oldvalue = textarea.value;
        var newvalue = oldvalue + "\n" + activity.uname +":" + YAHOO.util.Dom.get("chatenter").value;
        textarea.value = newvalue; 
	YAHOO.util.Connect.asyncRequest(request_type, url, null, postdata);
        YAHOO.util.Dom.get("chatenter").value = "";
   }, 

   updateView:function(message){
      var textarea = YAHOO.util.Dom.get("chattextarea");
      var oldvalue = textarea.value;
      var newvalue = message + "\n" + oldvalue;
      textarea.value = newvalue; 
   }     
}

LLDL.activities.Logout = function(activity) {
    this.body = document.createElement("div");
    this.body.id = "logoutview"; 
    this.body.appendChild(document.createTextNode(LLDL.bundle.activity_logout));
    this.body.activity = activity;
    YAHOO.util.Event.addListener(window,'click', this.logout); 
 }

LLDL.activities.Logout.prototype = {
 
   logout:function(e){
	var target = e.target;         
        if (target.id.toLowerCase() != 'logoutview'){return;}
        var activity = YAHOO.util.Dom.get("logoutview").activity;
        var  url = "?a=pr&o=xml&rt=r&ro=1&s="+activity.activityName+"&s1.service="+LLDL.activities.LOGOUT_SERVICE+"&c="+activity.collName;
	var request_type = "POST";
        var postdata = "&s1."+activity.ID_ATTR+"="+ activity.uid;                            
        YAHOO.util.Connect.asyncRequest(request_type , url,activity.logoutCallback , postdata); 
  }
}

LLDL.activities.BackButton = function(link) {
    this.body = document.createElement("div");
    this.body.id = "backbuttonview"; 
    this.body.innerHTML = "<a href="+link+">" +LLDL.bundle.activity_back+"</a>";
}

var timer;
var delay = 5000;
var globalcallback = null;
var globaluid = "";
var globalcollname = "";
var globalactivity = "";

startFetch = function(){
              
   var  url = "?a=pr&o=xml&rt=r&ro=1&s="+globalactivity+"&s1.service="+LLDL.activities.GET_MESSAGE_SERVICE+"&c="+globalcollname;
   var request_type = "POST";
   var postdata = "&s1.id="+ globaluid;  
   YAHOO.util.Connect.asyncRequest(request_type,url,globalcallback , postdata); 
   
   timer = setTimeout("startFetch()", delay);       
}

stopFetch = function(){
  clearTimeout(timer);
}


LLDL.CONST = {};
LLDL.CONST.COLLO_DOCID = 'docID';
LLDL.CONST.COLLOIDS = 'colloIDs';
LLDL.CONST.COLLOWORDS = 'collowords';
////The followings should have been defined within the namespace LLDL.CONST - needs fixing later (TODO!!!)//////////////////////////////
LLDL.NUM_INSTANCES = 'numInstances';
LLDL.NODEID = 'nodeid';
LLDL.NODEIDS = 'nodeids';
LLDL.DOCID = 'docid';
LLDL.IMGID = 'imgid';//used by ImageGuessing/DesignImageGuessing
LLDL.DB_ID = "dbid";
LLDL.DOC = 'doc';
LLDL.ITEM = 'item';
LLDL.DISCARD = 'discard';
LLDL.MESSAGE = 'message';
LLDL.TYPE = 'type';
LLDL.CHAT_TEXT = 'chattext';

LLDL.FOR_PRINT = 'forprint';
LLDL.FOR_DISPLAY = 'fordisplay';
LLDL.FOR_SAVE = 'forsave';

LLDL.OK = "1";
LLDL.NOTOK = "-1";
LLDL.YES = "yes";
LLDL.NO = "no";
LLDL.TEST_MODE = 0;

LLDL.PARA_START = "<";
LLDL.PARA_END = ">";

LLDL.ID = 'id';
LLDL.USER = 'user';
LLDL.USERID = 'userid';
LLDL.USERNAME = 'username';
LLDL.USERSCORE = 'userscore';
LLDL.IGMODE = 'IGMode';//activity mode - possible values: 'i', 'g', 'p' (sorry about this lagacy name)
LLDL.QWPOS = 'qwpos';//ContentWordGuessing - Question Word POSition
LLDL.QWLN = 'qwln';//ContentWordGuessing - the label number of the word
LLDL.QWCONT = 'qwcont';//ContentWordGuessing - Question Word content (ie, the actual word)
LLDL.AWPOS = "awpos";//ContentWordGuessing - Accomplished Word POSition
LLDL.SCORED_USER_LIST = 'scoredUserList';//ContentWordGuessing - group info tag name
LLDL.LOGIN_USER_LIST = 'loginUserList';//ContentWordGuessing - group info tag name
LLDL.SINGLE = 'single';//ImageGuessing
LLDL.MARRIED = 'married';//ImageGuessing
LLDL.HINT = 'hint';//ContentWordGuessing
LLDL.PARA_MARKER = 'P_P_';//ContentWordGuessing
LLDL.ROLE = 'role';
LLDL.PARTNER = 'partner';
//The following 3 are used by BuildCollections/flax-about.js
LLDL.IMAGE = 'img';
LLDL.AUDIO = 'aud';
LLDL.VIDEO = 'vid';
LLDL.TIME_LIMIT = 'timelimit';
LLDL.TIMESUP = 'timesup';
LLDL.EXERCISENAME = 'exercisename';

//These two separators MUST match the ones defined in flax/lib.php 
LLDL.id_separator = ',';
LLDL.text_separator = ':;:;:';
//These two separators MUST match the ones defined in flax/ws_gateway.php
LLDL.nv_separator = ';;';
LLDL.arg_separator = ']]';
//used as metadata separator in getDesignQueryParam() and parameter separator in createActivity()
LLDL.param_nv_separator = ';';
LLDL.param_arg_separator = ']';
LLDL.params = 'params';
LLDL.moduleParams = 'moduleParams';

LLDL.getHeader = function (title_text, flax_logo) {
	var header_container = LLDL.createEl('div', '', '', 'header_container');
	var logo_class_name = '';
	if(flax_logo) {
		logo_class_name = flax_logo + '_logo';
	}
	var header = LLDL.createInnerHTML('div', '', title_text, 'header '+logo_class_name);
	header_container.appendChild(header);
	return header_container;
}
LLDL.getFooter = function (text, flax_logo) {
    var footer_container = LLDL.createEl('div', '', '', 'footer_container');
    var logo_class_name = '';
    if(flax_logo) {
        logo_class_name = flax_logo + '_logo';
    }
    var footer = LLDL.createInnerHTML('div', ((text)? text :''), '<span class="dummy">.</span>', 'footer '+logo_class_name);
    footer_container.appendChild(footer);
    return footer_container;
}
/* Returns a page template that is used by all Activity classes as base container - student interface.
*  The ids and class names used in the function correspond to the selector names in the stylesheet file main.css
*/
LLDL.getActivityBody = function(container_id, title, add_close_window_btn/*boolean*/, header_with_logo/*boolean*/, footer_with_logo/*boolean*/) {
	var container = null;
	if(!container_id) {
		container = window.document.body;
	} else {
		container = (typeof container_id === 'string')? yud.get(container_id) : container_id;
	}
      
    if(LLDL.activities.DEBUG) {
        var log_body = LLDL.createEl('div', '__yahoo_logger_body');
        container.appendChild(log_body);
        LLDL.enableYahooLogger(log_body);
    }
        
    container.appendChild(LLDL.getHeader(title, (header_with_logo? 'student':false)));

    var activity_body = LLDL.createEl("div", "activity_body");
    container.appendChild(activity_body);
    container.appendChild(LLDL.getFooter('', (footer_with_logo?'student':false)));
//	if(add_close_window_btn) {
//      	yud.insertAfter(LLDL.createInnerHTML("div", "", "<button title='Close this exercise window or tab' onclick='window.close();'>Close window</button>", "close_window_button_container"), container);
//	}
    return activity_body;        
}
/* Returns a page template that is used by all DesignActivity classes as base container - teacher interface.
*  The ids and class names used in the function correspond to the selector names in the stylesheet file main.css
*/
LLDL.getDesignActivityBody = function(container_id) {
    var container = LLDL.createEl("div", ((container_id)?container_id:''));
    if(LLDL.activities.DEBUG) {
        var log_body = LLDL.createEl('div', '__yahoo_logger_body');
        container.appendChild(log_body);
        yud.setStyle(log_body, 'width', '100%');
        LLDL.enableYahooLogger(log_body);
    }

    return container;        
}
/* Returns a page template that is used by all Activity classes as base container to print out a plain, non-scroll version of the activity.
*  The ids and class names used in the function correspond to the selector names in the stylesheet file main.css
*/
LLDL.getPrintActivityBody = function(activity_title, images_url) {
    var header = LLDL.createInnerHTML('div', '', //'<img src="'+images_url+'flax-logo.png' + '"/>' +
            '<span>FLAX Exercise - '+activity_title+'</span>' +"<img src='"+images_url+'flaxteacherlogo.png' + "'/>" 
            , 'print_version_hd_class');
    document.body.appendChild(header);
    if(LLDL.activities.DEBUG) {
        var log_body = LLDL.createEl('div', '__yahoo_logger_body');
        header.appendChild(log_body);
        yud.setStyle(log_body, 'width', '100%');
        LLDL.enableYahooLogger(log_body);
    }
    var print_body = LLDL.createEl('div', '', '', 'print_version_body_class');
    document.body.appendChild(print_body);
    var footer = LLDL.createInnerHTML('div', '', '<p>Comments:</p><br/><br/>', 'print_version_ft_class');    
    document.body.appendChild(footer);
    return print_body;        
}
LLDL.log = function(msg) {
    if(LLDL.activities.DEBUG) {
        var text = msg;
        if(LLDL.startsWith(msg, '<')) {
            //Escape xml tags so that xml msg can be displayed
            text = text.replace(/</g, '&lt;');
            text = text.replace(/>/g, '&gt;');
        }
        YAHOO.log('][' + text);
//        LLDL.myLogWriter.log(msg);
    }
}
LLDL.enableYahooLogger = function(log_body) {
    var myLogReader=new YAHOO.widget.LogReader(log_body, {
        verboseOutput:false, newestOnTop:true, draggable:false, footerEnabled:false, top:"0px", left:"0px"
    });

    myLogReader.setTitle("");
    var log_head = yud.get('yui-log-hd0');
    if(log_head) {
        log_head.innerHTML = '';
    }
    myLogReader.formatMsg = function(oLogMsg) { 
	   var msg = oLogMsg.msg;
	   var time_stamp = getTime(oLogMsg); 
	   if(msg.indexOf('][') !== -1) {
	       var output = msg.substring(2);//remove '][' flag
	       return '[' + time_stamp + '] ' + output + '<br />';
       }
       return ''; 
	}; 
    myLogReader.hideCategory("warn"); 
	myLogReader.hideCategory("error"); 
	myLogReader.hideCategory("time"); 
	myLogReader.hideCategory("window"); 
	myLogReader.hideCategory("Connection"); 
//	myLogReader.hideCategory("info"); 
//	myLogReader.hideSource("global"); 
//	myLogReader.hideSource("LogReader"); 
    function getTime(msg) {
        if(msg.time) {
            var str = msg.time.toString();
            var reg = new RegExp(/\d\d?\s?:\s?\d\d?\s?:\s?\d\d?/);
            var time = reg.exec(str);
            return (time!=null)? time : '?';            
        }
        return '?';
    }
};
/*function is used by DesignActivity.js and DesignActivityModule.js
* It serves as documentation to let whoever uses it know what is expected by the classes to which the returned object is passed.
*/
LLDL.getDesignActivityConfigObject = function(  /*1: ajax_server_url*/         ajax_server_url, 
                                                /*2: help_server_url*/         help_server_url, 
                                                /*3: url to use in case error*/log_server_url, 
                                                /*4: images_url*/              images_url, 
                                                /*5: serving_activity_url*/    serving_activity_url, 
                                                /*6: caller_obj*/              caller_obj, 
                                                /*7: caller_obj_callback_fn*/  caller_obj_callback_fn, 
                                                /*8: ajax_fn*/                 ajax_fn, 
                                                /*9: coll_name*/               coll_name,
                                                /*10:is_teacher*/              is_teacher,
                                                /*11:lang*/                    lang                                                
                                             ) {
        var o = {
            ajax_server_url:ajax_server_url,
            help_server_url:help_server_url,
            log_server_url:log_server_url,
            images_url:images_url,
            serving_activity_url:serving_activity_url,
            caller_obj:caller_obj,
            caller_obj_callback_fn:caller_obj_callback_fn,
            ajax_fn:ajax_fn,
            coll_name:coll_name,
            is_teacher:is_teacher,
            lang:lang||'en'
        };
        return o;
}
//function is used by design activity bean classes (eg, DesignScrambleSentence.js)
//Stand-alone version may not use activity_nodeids and activity_nodecontents, but the Moodle module certainly needs them
//LLDL.getDesignActivityReturnObject = function(  /*1: activity_type*/activity_type, 
//                                                /*2: activity_link*/activity_link, 
//                                                /*3: activity_nodeids (array())*/activity_nodeids, 
//                                                /*4: activity_nodecontents (array())*/activity_nodecontents
//                                             ) {
//        var o = {
//            activity_type:activity_type,
//            activity_link:activity_link,
//            activity_nodeids:activity_nodeids,
//            activity_nodecontents:activity_nodecontents
//        };
//        return o;
//}
/* @param str - a string separated by arg_separator and nv_separator. 
                For example, if arg_separator is '&' and nv_separator is '=', then str could be something like a=a_value&b=b_value&c=c_value
   @return a HashMap with its keys as 'a', 'b', and 'c'; its values as 'a_value', 'b_value', and 'c_value'             
*/
LLDL.extractParams = function(str, arg_separator, nv_separator) {
    var map = new HashMap();
    var args = str.split(arg_separator);
    var arg=null, nvs = null;
    for(var i=0; i<args.length; i++) {
        arg = args[i];
        nvs = arg.split(nv_separator);
        map.put(nvs[0], nvs[1]);
    }
    return map;
}
/* @param map - a HashMap with its keys as 'a', 'b', and 'c'; its values as 'a_value', 'b_value', and 'c_value'
   @return a string separated by arg_separator and nv_separator. 
           For example, if arg_separator is '&' and nv_separator is '=', then the return value could be something like a=a_value&b=b_value&c=c_value
                
*/
LLDL.combineParams = function(map, arg_separator, nv_separator) {
    var str_arr = [];
    var keys = map.keySet();
    var values = map.valSet();
    for(var i=0; i<keys.length; i++) {
        str_arr[str_arr.length] = keys[i] + nv_separator + values[i];
    }
    return str_arr.join(arg_separator);
}
LLDL.attachAudioGadget = function(container, assocpath, aud_name_arr) {
	if(!container || !aud_name_arr || aud_name_arr.length < 1) return '';
	var container_el = (typeof container == 'string')? yud.get(container) : container;
//	// Insert the scripts needed for playing the audios
//    var head_el = window.document.getElementsByTagName('head')[0];
//    var new_script = window.document.createElement('script');
//    new_script.type = 'text/javascript';
//    new_script.src = 'interfaces/flax/js/core/audioplayer.js';
//    head_el.appendChild(new_script);
    aud_name_arr.sortString(true);
    var aud_menu = [], menu_id = 'aud_menu_id';
    aud_menu.push("<select id='"+menu_id+"'>");
    for(var aud_ttl, aud_html, i=0; i<aud_name_arr.length; i++) {
        aud_ttl = (aud_name_arr[i].length > 50)? aud_name_arr[i].substring(0, 50) + ' ...' : aud_name_arr[i];
        aud_html = "<option value='"+assocpath+aud_name_arr[i]+".mp3'>" + aud_ttl+"</option>";
        aud_menu.push(aud_html);     
    }
    aud_menu.push("</select>");
    var swf_src = 'interfaces/flax/mediaplayer/niftyplayer/niftyplayer.swf';
    var first_aud = "?file="+assocpath+aud_name_arr[0]+".mp3&as=0";//as stands for auto-start
    //In the following, <object classid> is for IE, while <embed> for FF/Chrome
    var audio_body = '' + aud_menu.join('') +
        '<span class="aud-object"><object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" ' +
        'codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0" ' +
        'width="165" height="20" id="niftyPlayer" align="top">' +
        '<param name=movie value="'+swf_src+first_aud+'">' +
        '<param name=quality value=high>' +
        '<param name=bgcolor value=#FFFFFF>' +
        '<embed src="'+swf_src+first_aud+'" quality=high bgcolor=#FFFFFF ' +
        'width="165" height="20" name="niftyPlayer" align="top" type="application/x-shockwave-flash" ' +
        'swLiveConnect="true" pluginspage="http://www.macromedia.com/go/getflashplayer"></embed>' +
        '</object></span>' +
        '';
    container_el.innerHTML = audio_body;//make sure it's dom ready b4 adding listeners, etc.   
	
    YAHOO.util.Get.script('interfaces/flax/js/core/audioplayer.js');   
//    var aud_bd = document.getElementById('aud_menu_id');    
//    var aud_fn = function() {
//        var aud = aud_bd.options[aud_bd.selectedIndex].value;
//        var np = audioplayer('niftyPlayer');
//        np.stop();
//        np.loadAndPlay(aud);
//    }
//    // Attach event listener
//    if(aud_bd.attachEvent){ //IE
//        aud_bd.attachEvent('onchange', aud_fn);
//    } else {
//    	aud_bd.addEventListener('change', aud_fn, false);
//    }     
    yue.on(menu_id, 'change', function(e) {
        var aud_bd = yue.getTarget(e);
        var aud = aud_bd.options[aud_bd.selectedIndex].value;
        var np = niftyplayer('niftyPlayer');
        np.stop();
        np.loadAndPlay(aud);
    });        
};
LLDL.attachTooltip = function() {
    var hint = new YAHOO.widget.Tooltip('coll_summary_tooltip', { context:coll_name_arr, autodismissdelay: 20000 } );
    hint.element.className = 'tooltip';
    hint.contextTriggerEvent.subscribe( 
        function(type, args) { 
            var context = args[0]; 
            hint.element.innerHTML = amm.collection_map.get(context.id).getSummaryInnerHTML();
            
        }); 
};

/** o - Object {original_h: oh, original_w: ow, max_scale_h: mh, max_scale_w: mw}
*   Return o = {height: h, width: w}
*/
//LLDL.scaleImageDimension = function(o){
//    var height, width;
//    var use_width = true;
//    if(o.original_h > o.original_w) {
//        use_width = false;
//    }
//    if(use_width) {
//        width = o.max_scale_w;
//        height = o.original_h*(o.max_scale_w/o.original_w);
////alert('rounded height='+Math.round(height)+' resulting height='+height+' original height='+o.original_h+' max_scale width='+o.max_scale_w+' original width='+o.original_w+' o.max_scale_w/o.original_w='+(o.max_scale_w/o.original_w));
//    } else {
//        height = o.max_scale_h;
//        width = o.original_w*(o.max_scale_h/o.original_h);
//    }
//    return {height: Math.round(height), width: Math.round(width)};
//}
/*This is used for loading cover images of documents*/
//LLDL.getImageHTML = function(img_src, desire_height, desire_width) {
//    var img_wrapper_id='img_wrapper_id', img_wrapper_class='cover_img_class', img_id = 'img_id';        
//    
//    if(!img_src) { return ""; }    
//    var io = new Image();
//    io.src = img_src.replace(/\\/g, '/');//Replace '\' with '/' (if any), because it's url not file path
//    io.onload = function() {
//        var o = LLDL.scaleImageDimension({original_h: io.height, original_w: io.width,
//                     max_scale_h: desire_height, max_scale_w: desire_width});
//        var img = yud.get(img_id);
//        img.setAttribute('height', o.height);
//        img.setAttribute('width', o.width);
//    }
//    var img_html = "<div class='cover_img_class'>" +
//                    "<img id='"+img_id+"' src='"+ img_src + "' alt='Picture source: "+img_src+"'/>" +
//                   "</div>";
//    return img_html;
//}
//LLDL.getImageHTML = function(img_id, img_src) {
//	var img_wrapper_id='img_wrapper_id', img_wrapper_class='cover_img_class';        
//	
//    if(!img_id || !img_src) { return ""; }    
//    var src = img_src.replace(/\\/g, '/');//Replace '\' with '/' (if any), because it's url not file path
//    var img_html = "<div class='cover_img_class'>" +
//            		"<img id='"+img_id+"' src='"+ src + "' alt='Picture source: "+src+"'/>" +
//    	           "</div>";
//    return img_html;
//}
//LLDL.scaleCoverImage = function(img_id, desire_height, desire_width) {
//    var io = yud.get(img_id);
//    io.onload = function() {
//        var o = LLDL.scaleImageDimension({original_h: io.height, original_w: io.width,
//                     max_scale_h: desire_height, max_scale_w: desire_width});
//        io.setAttribute('height', o.height);
//        io.setAttribute('width', o.width);
//    }
//}
LLDL.assert = function(callerFunctionName, bCondition, sErrorMessage) {
	if (bCondition) {
		throw new Error ("----ERROR----: in " + callerFunctionName + ", " + sErrorMessage);
		return false;
	}
	return true;
}
LLDL.removeChildren = function (node_id, callerName) {
	var node = yud.get(node_id);
	if (LLDL.assert(callerName, node==null, node_id + " does not exist.") == true) {
		while (node.hasChildNodes()) {
	    	node.removeChild(node.firstChild);
		}
		return node;
	}
}
////////// DOM util functions
/////////// FLAX html library for creating html components ////////////
var UI = {vars:{}};
//UI.vars.type_or_select = 'Type or select';
/* For this ComboInputBox to work: 
* 1. textbox and selection list must be already appended to the dom tree, and cannot be set to visibility:hidden or display:none.
* 2. combo_config = {width:number(the width must be the width of the textbox plus the width of the dropdown menu.)}
* 3. select_obj: an instance of UI.SingleSelectDropdownList()
* 4. textbox_obj: an instance of UI.TextBox
* */
/*
UI.ComboInputBox = function(container, combo_config, dropdown_obj) {
   this.textbox = this.getTextbox();
   this.dropdown_obj = dropdown_obj;
   container.appendChild(this.textbox);
   container.appendChild(this.dropdown_obj.body);
   var combo_width = (typeof combo_config.width === 'Number')? combo_config.width : parseInt(combo_config.width, 10);
   this.setStyle(this.textbox, this.dropdown_obj.body, combo_width);
   this.addEvents(this.textbox, this.dropdown_obj, combo_config);
};
UI.ComboInputBox.prototype = {
   get: function() {
       return this.textbox.value;
   },
   set: function(value) {
       this.textbox.value = value;
       this.dropdown_obj.set(value);
   },
   addToMenu: function(value) {
       this.drdropdown_obj.addMenuOption(value);
   },
   addEvents: function(textbox, dropdown_obj, combo_config) {
//      yue.on(dropdown_obj.body, 'focus', function(e){
//          textbox_width = combo_config.width - 18;
//              yud.setStyle(dropdown_obj.body, 'left', ''+textbox_width * (-1)+'px');
//            yud.setStyle(dropdown_obj.body, 'width', ''+combo_config.width+'px');
//      }, this, true);
//        yue.on(dropdown_obj.body, 'blur', function(e){
//            yud.setStyle(dropdown_obj.body, 'width', '18px');
//                yud.setStyle(dropdown_obj.body, 'left', '0px');
//        }, this, true);
       yue.on(dropdown_obj.body, 'click', function(e){
           var select_value = dropdown_obj.get();
           if(textbox.value !== select_value) {
               textbox.value = select_value;
           }
       }, this, true   );  },
   getTextbox: function() {
       var el = LLDL.createEl('input');
       el.setAttribute('type', 'text');
       el.setAttribute('value', 'Type or select');
       el.setAttribute('title', 'Type or select combo input');
//        el.setAttribute("onmousedown","this.value=''; this.onmousedown=null;this.focus();");
       return el;
   },
   getDropdown: function(config) {},
   setStyle: function(textbox, dropdown, combo_width) {
       
       yud.setStyle(textbox, 'vertical-align', 'bottom');
       yud.setStyle(textbox, 'border', 'solid 1px #7B9EBD');// #7B9EBD is the naturally rendered border color by Firefox for form fields such as textbox.
       yud.setStyle(textbox, 'position', 'relative');
       yud.setStyle(textbox, 'height', 22+'px');
       yud.setStyle(dropdown, 'margin-right', 0+'px');
       yud.setStyle(dropdown, 'padding-right', 0+'px');
       yud.setStyle(dropdown, 'vertical-align', 'bottom');
       yud.setStyle(dropdown, 'border', 'solid 1px #7B9EBD');
       yud.setStyle(dropdown, 'position', 'relative');
       yud.setStyle(dropdown, 'height', 22+'px');
       yud.setStyle(dropdown, 'margin-left', 0+'px');
       yud.setStyle(dropdown, 'padding-left', 0+'px');
       
       yud.setStyle(dropdown, 'z-index', 0);
       yud.setStyle(textbox, 'z-index', 1);
       
       yud.setStyle(textbox, 'border-right', 'none');

       //The dropdown arrow (on the right end of a select menu) is measured 18 pixels wide.        
       var textbox_width = Math.round(combo_width - 18);
       yud.setStyle(textbox, 'width', textbox_width+'px');
       yud.setStyle(dropdown, 'width', 22+'px');
//        yud.setStyle(dropdown, 'border-left', 'none');
//        yud.setStyle(dropdown, 'border-right', 'solid 1px #7B9EBD');
   }
};*/

/**TODO: A possibly better approach to implementing this combo box could be to use JS events to create a virtual 'text box' on top of a select menu
 *       on the fly, and instantly insert what the user types as option value into the select menu by monitoring keyboard strokes. 
 * For this ComboInputBox to work: 
 * 1. container must be already appended to the dom tree.
 * 2. combo_config = {width:number(the width must be the width of the textbox plus the width of the dropdown menu. Better not append any other dom elements on the same line after this combo.)}
 * 3. dropdown_obj: an instance of UI.SingleSelectDropdownList()
 * */
UI.ComboInputBox = function(container, combo_config, dropdown_obj) {
	this.textbox = this.getTextbox();
	this.dropdown_obj = dropdown_obj;
	container.appendChild(this.textbox);
    container.appendChild(this.dropdown_obj.body);
    var combo_width = (typeof combo_config.width === 'Number')? combo_config.width : parseInt(combo_config.width, 10);
    this.setStyle(this.textbox, this.dropdown_obj.body, combo_width);
    this.addEvents(this.textbox, this.dropdown_obj);
};
UI.ComboInputBox.prototype = {
	get: function() {
		return this.textbox.value;
	},
	set: function(value) {
		this.textbox.value = value;
		this.dropdown_obj.set(value);
		
		// If this function is called, it it to set any values but the initial one ('Type or select'). So change it back to normal style
//		this.setNormalStyle(this.textbox);
	},
	addToMenu: function(value) {
		this.dropdown_obj.addMenuOption(value);
	},
	addEvents: function(textbox, dropdown_obj) {
        yue.on(dropdown_obj.body, 'click', function(e){
			var el = yue.getTarget(e);console.log(el.tagName);
//       	    if(el.tagName.toLowerCase() !== 'option') { 
//				//user only clicks to expand the menu, not to select a value
//				return; 
//			}
//            this.setNormalStyle(textbox);
            var select_value = dropdown_obj.get();
            if(textbox.value !== select_value) {
            	textbox.value = select_value;
            }
            console.log(select_value+' '+textbox.value);
            
        }, this, true   );	},
	getTextbox: function() {
        var el = LLDL.createEl('input');
        el.type = 'text';
        el.value = '';
        el.title = 'Type or choose from list';
//        el.setAttribute('type', 'text');
//        el.setAttribute('value', '');
//        el.setAttribute('title', 'Type or choose from list');
//		this.setInitStyle(el);
//		yue.on(el, 'mousedown', function(e) {
//			el.value = '';
//			this.setNormalStyle(el);
//			el.focus();
//			yue.removeListener(el, 'mousedown');
//		}, this, true);
        // el.setAttribute("onmousedown","this.value='';this.style.color='inherit';this.style.fontStyle='normal'; this.onmousedown=null;this.focus();");
        return el;
	},
	setInitStyle: function(el) {
	    if(!el) return;
		el.style.color = '#EBC0C5';//lbb.semi_transparent_color
		el.style.fontStyle = 'italic';
	},
	setNormalStyle: function(el) {
		if(!el) return;
		el.style.color='inherit';
		el.style.fontStyle='normal';
	},
	getDropdown: function(config) {},
	setStyle: function(textbox, dropdown, combo_width) {

//	    yud.setStyle(textbox, 'vertical-align', 'middle');
	    yud.setStyle(textbox, 'border', 'none');
        yud.setStyle(textbox, 'border-left', 'solid 1px #7B9EBD');// #7B9EBD is the border color naturally rendered by Firefox for form fields such as textbox.
	    yud.setStyle(textbox, 'position', 'absolute');
	    yud.setStyle(textbox, 'height', 20+'px');
	    yud.setStyle(dropdown, 'margin-right', 0+'px');
	    yud.setStyle(dropdown, 'padding-right', 0+'px');
//	    yud.setStyle(dropdown, 'vertical-align', 'middle');
	    yud.setStyle(dropdown, 'border', 'solid 1px #7B9EBD');
	    yud.setStyle(dropdown, 'position', 'absolute');
	    yud.setStyle(dropdown, 'height', 22+'px');
	    yud.setStyle(dropdown, 'margin-left', 0+'px');
	    yud.setStyle(dropdown, 'padding-left', 0+'px');
	    
	    yud.setStyle(dropdown, 'z-index', 0);
	    yud.setStyle(textbox, 'z-index', 1);
	    
        //The dropdown arrow (on the right end of a select menu) is measured 18 pixels wide.        
        var textbox_width = Math.round((combo_width - 18)/2);
        yud.setStyle(textbox, 'width', textbox_width+'px');
        yud.setStyle(dropdown, 'width', (textbox_width+18)+'px');
        yud.setStyle(textbox, 'margin-top', '1px');
	}
};
UI.RadioButton = function(container, config_arr/*[{name:'',value:'',title:'',checked:boolean, newline:boolean}]*/) {
    this.body = container; 
    for(var config, i=0; i<config_arr.length; i++) {
        config = config_arr[i];
        var el = LLDL.createEl('span', '','','block_value');
        var check = (config.checked)?'checked':'';
        el.innerHTML = "<input type='radio' name='"+config.name+"' value='"+config.value+"' " + check + " />";
/** The following is not IE friendly        
        var el = LLDL.createEl('input');
        el.setAttribute('type', 'radio');
        el.setAttribute('name', config.name);
        el.setAttribute('value', config.value);
        if(config.checked){
            el.setAttribute('checked', config.checked);
        }
*/        
        this.body.appendChild(el);
        this.body.appendChild(LLDL.createEl('span','',config.title));//document.createTextNode(config.title));
        if(config.newline){
            this.body.appendChild(LLDL.createEl('br'));
        }
    }
};
UI.RadioButton.prototype = {
    get: function() {
        var radio_group = yud.getElementsBy(function(e){ return (e.checked);
        }, "input", this.body);
        
        if(!radio_group) {return null;}
        return radio_group[0].value;
    },
    set: function(value) {
        yud.getElementsBy(function(e){ return true;
        }, "input", this.body, function(el) {
        	el.checked = (el.value === value)? 'checked' : null;
        });
    }
};
UI.TextBox = function(config/*{name:'name',value:'value',size:'size',title:'title'}*/) {
    var el = LLDL.createEl('input');
    el.setAttribute('type', 'text');
    el.setAttribute('name', config.name);
    el.setAttribute('value', config.value);
    el.setAttribute('size', config.size);
    el.setAttribute('title', config.title);
    this.body = el;
};
UI.TextBox.prototype = {
    get: function() {
        return this.body.value;
    },
    set: function(value) {
        this.body.value = value;
    }
};
UI.TextArea = function(config/*{name:'name',cols:'80',rows:'50',value:''}*/) {
    var el = LLDL.createEl('textarea');
    el.setAttribute('name', config.name);
    el.setAttribute('cols', config.cols);
    el.setAttribute('rows', config.rows);
    el.appendChild(document.createTextNode(config.value));
    this.body = el;
};
UI.TextArea.prototype = {
    get: function() {
        return this.body.firstChild.nodeValue;
    },
    set: function(value) {
        this.body.firstChild.nodeValue = value;
        
    }
};
UI.CheckBox = function(obj) {
    this.body = this.getBody(obj);
    this.isChecked = obj.checked;
};
UI.CheckBox.prototype = {
    getBody : function(o) {
    	var box = LLDL.createEl('input');
    	box.type = 'checkbox';
    	box.name = o.name;
    	box.value = o.value;
    	box.checked = o.checked;
//    	box.innerHTML = "<input type='checkbox' name='"+o.name+"' value='" + o.value+"'" +
//    			((o.checked)?"checked":"");
        yue.on(box, 'click', function(){
        	this.isChecked = (this.isChecked)? false : true;
        }, this, true);
        
        return box;
    },
    set : function(v) {
    	this.isChecked = v;
//        var checkbox_group = this.body.getElementsByTagName('input');
//        for(var i=0; i<checkbox_group.length; i++) {
//            if(checked_value_array.indexOf(checkbox_group[i].value) !== -1){
//                checkbox_group[i].checked = true;
//            }
//        }
    },
    get : function(){
    	return this.isChecked;
//        var checkbox_parent_el = this.body;
//        var checkbox_group = yud.getElementsBy(function(e){ return e.checked; }, "input", checkbox_parent_el); 
//        if(!checkbox_group) return false;
//        var value_arr = [];
//        for(var i=0; i<checkbox_group.length; i++) {
//            value_arr.push(checkbox_group[i].value);
//        }
//        //if value_arr is an empty array, value_arr.join(",") results in "" (empty string [typeof is string])
//        //same result can be given by using either value_arr.toString() or value_arr.join() with no arguments
//        return value_arr;
    }
}
/*[{value:'value',title:'title',selected:true},{value:'value',title:'title',selected:false},{value:'value',title:'title',selected:false}]
 * */
UI.SingleSelectionList = function(optionObj_array) {
    this.body = this.getBody(optionObj_array);
};
UI.SingleSelectionList.prototype = {
    getBody : function(optionObj_array) {
        
        var oo, option;
        var select_el = LLDL.createEl("select");
        yud.setStyle(select_el, 'width', '100px');
        for(var i=0; i<optionObj_array.length; i++) {
            oo = optionObj_array[i];
            option = new Option(oo.title, oo.value, false, oo.selected); 
            select_el.options[select_el.options.length] = option;
        }
        return select_el;
    },
    set : function(selected_option_value_or_index) {
    	if(typeof selected_option_value_or_index == 'number'){
    		this.body.selectedIndex = selected_option_value_or_index;
    		return;
    	}
    	if(typeof selected_option_value_or_index == 'string' && !selected_option_value_or_index) { return; }
        var options = this.body.getElementsByTagName('option');
        for(var i=0; i<options.length; i++) {
            if(options[i].value === selected_option_value_or_index) {
                options[i].selected = true;
                break;
            }
        }
    },
    get : function() {
        return this.body.options[this.body.selectedIndex].value;
    },
    getTitle : function() {
        return this.body.options[this.body.selectedIndex].text;
    },
    addMenuOption: function(value) {
    	var options = this.getMenuOptions();
    	if((options).indexOf(value) === -1) {
    		var title = value;
    		if(title.length > 20) { title = title.substring(0, 15) + ' ...'; }
            this.body.options[this.body.options.length] = new Option(title, value, false, false);
    	}
    },
    //add to the beginning of the option list
    addFirstMenuOption: function(value) {
    	var previous_selected_index = this.body.selectedIndex; 
    	this.body.selectedIndex = 0;
    	this.insert(value,value);
    	this.body.selectedIndex = previous_selected_index+1;
    	return;
    },
    // Return all options contained in the menu as an array of string
    getMenuOptions: function() {
        var options = [];
        for(var i=0; i<this.body.options.length; i++) {
            options.push(this.body.options[i].value);
        }
        return options;
    },
    insert:function(newText, newValue) {
      if (this.body.length == 0) {
        var newOpt1 = new Option(newText, newValue);
        this.body.options[0] = newOpt1;
        this.body.selectedIndex = 0;
      } else if (this.body.selectedIndex != -1) {
        var selText = new Array();
        var selValues = new Array();
        var selIsSel = new Array();
        var newCount = -1;
        var newSelected = -1;
        var i;
        for(i=0; i<this.body.length; i++) {
          newCount++;
          if (newCount == this.body.selectedIndex) {
            selText[newCount] = newText;
            selValues[newCount] = newValue;
            selIsSel[newCount] = false;
            newCount++;
            newSelected = newCount;
          }
          selText[newCount] = this.body.options[i].text;
          selValues[newCount] = this.body.options[i].value;
          selIsSel[newCount] = this.body.options[i].selected;
        }
        for(i=0; i<=newCount; i++) {
          var newOpt = new Option(selText[i], selValues[i]);
          this.body.options[i] = newOpt;
          this.body.options[i].selected = selIsSel[i];
        }
      }
    },
    append:function(newText, newValue)    {
      if (this.body.length == 0) {
        var newOpt1 = new Option(newText, newValue);
        this.body.options[0] = newOpt1;
        this.body.selectedIndex = 0;
      } else if (this.body.selectedIndex != -1) {
        var selText = new Array();
        var selValues = new Array();
        var selIsSel = new Array();
        var newCount = -1;
        var newSelected = -1;
        var i;
        for(i=0; i<this.body.length; i++)        {
          newCount++;
          selText[newCount] = this.body.options[i].text;
          selValues[newCount] = this.body.options[i].value;
          selIsSel[newCount] = this.body.options[i].selected;
          
          if (newCount == this.body.selectedIndex) {
            newCount++;
            selText[newCount] = newText;
            selValues[newCount] = newValue;
            selIsSel[newCount] = false;
            newSelected = newCount - 1;
          }
        }
        for(i=0; i<=newCount; i++)        {
          var newOpt = new Option(selText[i], selValues[i]);
          this.body.options[i] = newOpt;
          this.body.options[i].selected = selIsSel[i];
        }
      }
    },
    remove:function()    {
      var selIndex = this.body.selectedIndex;
      if (selIndex != -1) {
        for(i=this.body.length-1; i>=0; i--) {
          if(this.body.options[i].selected) {
            this.body.options[i] = null;
          }
        }
        if (this.body.length > 0) {
          this.body.selectedIndex = selIndex == 0 ? 0 : selIndex - 1;
        }
      }
    }    
};

LLDL.checkAll = function(el/**id or elem*/, check_all/**boolean*/) {
/**
 * Check or uncheck all check boxes found within el
 */	
 	if(!el || typeof check_all != 'boolean') return;
 	var body = (typeof el == 'string')? yud.get(el) : el;
 	var box_list = yud.getElementsBy(function(el) { return el.type == 'checkbox'; }, "input", body);
	for(var i=0; i<box_list.length; i++) {
		box_list[i].checked = check_all;
	} 
}
//////////////////////////////////////////////////////////////////////////////
LLDL.getDomFirstChildTextNodeValue = function(parent_el, child_tag_name) {
	if(!parent_el || !child_tag_name) { return ''; }
    var e = parent_el.getElementsByTagName(child_tag_name)[0];
    if(!e) { return ""; }
    if(e.firstChild) {
        return LLDL.trim(e.firstChild.data);
    } 
    return "";   
}
LLDL.getNodeValue = function(el) {
	if(!el) { return ''; }
    if(!el.firstChild) { return ''; }
    return el.firstChild.nodeValue;   
}
//return the first child element under parent_el whose tag name is child_tag_name and an attribute named attr_name of value attr_value
LLDL.getNamedElement = function(parent_el, child_tag_name, attr_name, attr_value) {
    if(!parent_el || !child_tag_name || !attr_name || !attr_value) { return "null arguments"; }
    var nodelist = parent_el.getElementsByTagName(child_tag_name);
    if(!nodelist || nodelist.length < 1) { return null; }    
    for(var e=null, ename=null, i=0; i<nodelist.length; i++) {
        e = nodelist[i];
        ename = e.getAttribute(attr_name);
        if(ename === attr_value) {
            return e;
        }
    }
    return null;//not found
}
//return all elements under parent_el whose tag name is child_tag_name and an attribute named attr_name of value attr_value
LLDL.getNamedElements = function(parent_el, child_tag_name, attr_name, attr_value) {
    if(!parent_el || !child_tag_name || !attr_name || !attr_value) { return "null arguments"; }
    var nodelist = parent_el.getElementsByTagName(child_tag_name);
    if(!nodelist || nodelist.length < 1) { return null; }
    var newlist = [];
    for(var e=null, ename=null, i=0; i<nodelist.length; i++) {
        e = nodelist[i];
        ename = e.getAttribute(attr_name);
        if(ename === attr_value) {
            newlist[newlist.length] = e;
        }
    }
    return newlist;
}
LLDL.createInnerHTML = function(tag_name, id_str, inner_html, class_name) {
	var el = document.createElement(tag_name);
	if (inner_html) { 
	    el.innerHTML = inner_html; 
	}
	if (id_str) { el.id = id_str; }
	if (class_name) { el.className = class_name; }
	return el;	
}
LLDL.createButtonEl = function(id, value, class_name) {    
    var v = LLDL.createEl('input');
    if(id) {
        v.id = id;
    }
    v.setAttribute('type', 'button');
    v.setAttribute('value', value);
    // Do not use method setAttribute('class', class_name), as IE does not support it.
    v.className = class_name || 'activitybutton';
    
    return v;
}    
LLDL.createEl = function (tag_name, id_str, node_text, class_name) {
	var el = document.createElement(tag_name);
	if (id_str) { el.id = id_str; }
	if (class_name) { el.className = class_name; }
	if (node_text && node_text.length > 0) {
        el.appendChild(document.createTextNode(node_text));
	}
	return el;	
}
LLDL.clearup = function(node) {
	if(!node) return;
    while (node.hasChildNodes()) {
	    node.removeChild(node.firstChild);
	}    	
}
LLDL.animateRollin = function(el) {
    var anim = new YAHOO.util.Anim(el, { height: { from: 100, to: 0, unit: '%' }}, 1, YAHOO.util.Easing.easeIn);
    anim.animate(); 
    yud.setStyle(el, 'display', 'none');
}
LLDL.animateRollout = function(el) {
    yud.setStyle(el, 'display', '');
    var anim = new YAHOO.util.Anim(el, { height: { from: 0, to: 100, unit: '%' }}, 1, YAHOO.util.Easing.easeIn);
    anim.animate(); 
}
LLDL.animScrollIntoView = function(el/*element id or HTML element object (N.B. element must be present in DOM and visible)*/, time_duration/*int*/) {
	var elem = (typeof el === 'string')? yud.get(el) : el ;
	var y_axis = yud.getY(elem);
	var attributes = { scroll : { to: [0, y_axis] } };
	var anim = new YAHOO.util.Scroll(document.body, attributes, time_duration);
	anim.animate();
}
LLDL.trim = function(sInString) {
	if(sInString) { 
    	var str = sInString.replace( /^\s+/g, "" );// strip leading
    	str = str.replace( /\s+$/g, "" );// strip trailing
    	return str;
	}
	return sInString;
}
LLDL.startsWithDigit = function(str) {
    return !isNaN(parseInt(str));
}
LLDL.startsWith = function(str, sub_str) {
    //returns true if str starts with sub_str 
    // Alternative, if(str.search(new RegExp('^'+sub_str)) != -1)
    return (new RegExp('^' + sub_str)).exec(str) !== null;
}
LLDL.endsWith = function(str, sub_str) {
    //returns true if str ends with sub_str
    return (new RegExp(sub_str + '$')).exec(str) !== null;
}
LLDL.isPunctuation = function(a_char){
    var punc = ['`','~','!','@','#','$','%',
                '^','&','*','(',')','-','_','=','+','[',
                ']','{','}','\\','|',';',':','‘','’',
                '\'','"','“','”',',','.','<','>','/','?'];

//	var punc = " `~!@#$%^&*()-_=+[{]}\\|;:‘’'\"“”,<.>/?";
	return (punc.indexOf(a_char) != -1);
}
/**
 * This function is used by both BuildCollection.js/AttachFileGadget and flax-about.js/queryDocumentCallback
 * 
 * @param content - string in the format: <media image="name1.jpg,name2.jpg" audio="name1.wav,name2.mp3" video="name1.mp4,name2.mov"/>
 *                                        <p> ...document main content goes here ...</p>
 * @param media_type - string {image, audio, video}
 * @return array of string - containing value of the attribute of corresponding media_type
 */
LLDL.parseMediaFileNames = function(content, media_type) {
    //retrieve the element "<media image="name1.jpg,name2.jpg" audio="name1.wav,name2.wav" video="name1.mp4,name2.mp4"/>
    var media_tag = content.match(/<media.+?\/>/);
    if(media_tag == null) { console.log("Doc content doesn't contain <media ../> element"); return; }
    var pattern = media_type + '=[^\\s]+';
    var file_name_list = media_tag[0].match(new RegExp(pattern));
    if(file_name_list == null) { console.log("Pattern: "+pattern +" failed"); return; }
    var filenames = file_name_list[0];
    if(filenames.length <= 8){
        return null;//empty attribute value (eg, image="")
    }
    filenames = filenames.substring(7, filenames.length-1);//trim the leading image=" and the trailing "
    return filenames.split(/,/);
}
LLDL.pauseOnAction = function(xml, o) {
	//Pause some time for demo purposes (when client and server are on the same machine)
	window.setTimeout(function() { 
		o.fn.call(o.scope, xml, o.passover_obj); 
	}, 2000);
};
// An overlay showing a list of words  - utilized so far by activities WordGuessing and CompletingCollocations
LLDL.getWordListPanel = function(container, word_list_html, images_url) {
    var info_image = "<img src='" + images_url + "info16_1.gif' />";
    var icon = LLDL.createInnerHTML('div', '', info_image);
    icon.setAttribute('title', 'Show word list');
    container.appendChild(icon);
    yud.setStyle(icon, 'text-align', 'right');
    var close_btn_id = '_hide_word_list';
    var body = LLDL.createInnerHTML('div','','<div class="hd" style="color:#000000;background-color:#EEFFEE;border:none;margin:0;padding:0;">Words to guess</div><div class="bd">'+word_list_html+'</div>');
    container.appendChild(body);
    yud.setStyle(body, 'background-color', '#EEFFEE');
    yud.setStyle(body, 'color', 'blue');
    yud.setStyle(body, 'border', '1px solid #000000');
    var panel = new YAHOO.widget.Panel(body, {
    	context:[icon, 'br','br',['beforeShow','windowResize']],//beforeShow and windowResize events seem not working
    	close:true,
    	visible:false,
    	draggable:true,
    	underlay:'matte',
    	width:'auto',
    	constraintoviewport:true
    });
    panel.render();
    panel.show();//By default, don't show word list
    yue.on(icon, 'click', function(){ panel.show(); }, this, true);
};
LLDL._help_info_panel = null;
LLDL._help_info_map = new HashMap(); 
LLDL.attachHelpInfoLoader = function() {//attach event for help buttons
    var panel_body = LLDL.createInnerHTML('div','_FLAXHelpInfo','<div class="hd" style="border:none;background-color:#FFFF55;color:#000000;">title</div><div class="bd" style="background-color:#FFFF55;padding:10px 20px 5px 5px"/>');//The 20px padding-top is to accommodate the close button
    document.body.appendChild(panel_body);
    LLDL._help_info_panel = new YAHOO.widget.Panel(panel_body, {
    	close:true,
    	visible:false,
    	draggable:false,
//    	width:'auto',
    	height:'auto',
    	constraintoviewport:true
    });
    LLDL._help_info_panel.render();
    yud.setStyle(LLDL._help_info_panel.element, 'max-width', '400px');
    LLDL._help_info_panel.hide();//By default, don't show word list
    this.displayPanel = function(anchor_el, header, body) {
        LLDL._help_info_panel.setHeader(header);
   		LLDL._help_info_panel.setBody(body);
   		LLDL._help_info_panel.cfg.setProperty('context', [anchor_el, 'tl', 'br']);//attach to anchor element's bottom-right
   		LLDL._help_info_panel.show();
    }
    this.queryInfo = function(map_key, infoid, lang, url, anchor_el, infotitle) {
        var obj = {caller_obj: this,
                   callback_fn: this.queryInfoCallback,
                   http_method: 'POST',
                   url: url,
                   err_msg: 'Error occurred when loading help info: '+infoid,
                   passover_obj: [map_key, anchor_el],
                   postdata: '&s=FLAXHelp&s1.infoid='+infoid+'&s1.lang='+lang,
                   show_loading_panel: false,
                   loading_img_url: null};
        LLDL.talk2server(obj);           	
    }
    this.queryInfoCallback = function(xml, arr/*[map_key, anchor_el]*/) {
    	var info = LLDL.getDomFirstChildTextNodeValue(xml, 'info');
   		LLDL._help_info_panel.setBody(info);
    	LLDL._help_info_map.put(arr[0], info);
    }
    yue.on(window, "click", function(e) {
    	if(LLDL._help_info_panel) {
    		if(LLDL._help_info_panel.cfg.getProperty('visible')===true){
    			LLDL._help_info_panel.hide();
    		}
    	}
    	var el = yue.getTarget(e);
    	if(el.tagName.toLowerCase() !== 'img') return;
    	var infoid = el.getAttribute('_helpinfoid');
    	var infotitle = el.getAttribute('_helpinfotitle');
    	var lang = el.getAttribute('lang');
    	var images_url = el.getAttribute('images_url');
    	var server_url = el.getAttribute('server_url');
    	if(!server_url||!infoid || !lang || !images_url) return;
    	var map_key = infoid + '.' + lang;
    	var info = LLDL._help_info_map.get(map_key);
    	if(info) {    		
    		this.displayPanel(el, infotitle, info);
    	}else{
    		this.displayPanel(el, infotitle, '<img src="' + images_url + 'firefox_loading.gif"/>');
    		//retrieve info from server
    		this.queryInfo(map_key, infoid, lang, server_url, el, infotitle);
    	}
    }, this, true);	
};
LLDL.getHelpButtonEl = function(cfg, infoid, lang, info_label) {
	if(!cfg.ajax_server_url||!cfg.images_url||!infoid||!lang) console.log('Missing arguments in getHelpButtonEl');
	if(!LLDL._help_info_panel) LLDL.attachHelpInfoLoader();
    var msg_title = info_label;
    var btn = LLDL.createEl("img", "", "", "help_button_class");
    btn.setAttribute('src', cfg.images_url+'help.gif');
    btn.setAttribute('_helpinfoid', infoid);
    btn.setAttribute('_helpinfotitle', msg_title);
    btn.setAttribute('lang', lang);
    btn.setAttribute('images_url', cfg.images_url);
    btn.setAttribute('server_url', cfg.help_server_url);
    return btn;
}
LLDL.getHelpButtonInnerHTML = function(cfg, infoid, lang, info_label) {
	if(!cfg.ajax_server_url||!cfg.images_url||!infoid||!lang) console.log('Missing arguments in getHelpButtonInnerHTML');
	if(!LLDL._help_info_panel) LLDL.attachHelpInfoLoader();
    var msg_title = info_label;
	var src = cfg.images_url + 'help.gif';
    var html = '<img src="'+ src +'"' +
    		' class="help_button_class"' +
    		' _helpinfoid="'+infoid+'"'+
    		' _helpinfotitle="'+msg_title+'"'+
    		' lang="'+lang+'"'+
    		' images_url="'+cfg.images_url+'"'+
    		' server_url="'+cfg.help_server_url+'"/>';
    return html;
}
LLDL.openpopup = function(url, wname, options/*expecting height and width*/) {
	var default_options = 
//	        'menubar=no,' +
//			'location=no,' +
//			'toolbar=no,' +
//			'titlebar=no,' +
//			'status=no,' +
//          'z-lock=no,' +
//			'resizable=no,' +
            'scrollbars,' +
			'dependent,' +
			'alwaysRaised,';
	if(options) {
    	default_options += options;
	} else {
		default_options += 'width=550,height=500';
	}
    var wobj = window.open(url, wname, default_options);
    wobj.focus();
    return wobj;   
}
LLDL.getCallerFnName = function(arg){
	var caller_fn = arg.callee.caller.toString();
	return (caller_fn.substring(caller_fn.indexOf('function')))	
}
LLDL.setButtonDisable = function(btn/*id or html element*/, disabled) {
	if(!btn) { return; }
    var v;    
    if ("string" === typeof btn) {
        v = yud.get(btn);
    } else {
        v = btn;
    }
    if(v) {
        v.disabled = disabled;
    }
}
LLDL.setElementsStyle = function(el_arr, style_name_arr, style_value_arr) {
	if(!el_arr || !style_name_arr || !style_value_arr) { return false; }
	var len = el_arr.length;
    if(len !== style_name_arr.length || len !== style_value_arr.length) { return false; }
    for(var i=0; i<el_arr.length; i++) {
        yud.setStyle(el_arr[i], style_name_arr[i], style_value_arr[i]);
    }
}
LLDL.addKeyListener = function(e/*id or html element*/, keycode/*integer*/, handler/*function*/, handler_scope_obj/*object*/) {
    if(!e || !keycode || !handler || !handler_scope_obj) { return false; }
    var el;    
    if ("string" === typeof e) {
        el = yud.get(e);
    } else {
        el = e;
    }
	var key_listener = new YAHOO.util.KeyListener(el, {keys: keycode}, {fn: handler, scope: handler_scope_obj, correctScope:true});
	key_listener.enable();

	return key_listener;
}
LLDL.switchToErrorView= function(err_msg, server_log_url, expt, xtra_msg){ 
    var el = yud.get("page_body_id");
    if(!el) { 
        el = yud.get("frame");// in Moodle, activity is displayed in a frame
    }
    if(!el) {
        var els = document.getElementsByTagName("body");
        if(els && els[0]) { el = els[0]; }
    }
    if(!el) {//if still 
        document.write(err_msg);
        return;
    }
    
    LLDL.clearup(el);
    el.appendChild(LLDL.createEl("h2", "", err_msg));
/*TODO: logging errors needs to use talk2server()
    if(!server_log_url) {
        return true;
    }
    if(expt) {  
        var file_name = expt.fileName;
        var line_num = expt.lineNumber;
        var fn_name = arguments.callee.caller.name;
        if(!fn_name) {
            //anoynomous functions will have an empty name, then we extract the signature of the function
            var error_fn_body = arguments.callee.caller.toString();
            fn_name = error_fn_body.substring(0, error_fn_body.indexOf(")")+1);
        }
        this.logError(server_log_url, expt + ((xtra_msg)?xtra_msg:"") + " File: " + file_name + " Function: " + fn_name + " Line: "+ line_num);
    } else if(xtra_msg){
        LLDL.logError(server_log_url, xtra_msg);
    }  
    */      
}
LLDL.logError = function(server_url, msg){ 
    var postdata = "s1.error="+encodeURIComponent(msg);
    YAHOO.util.Connect.asyncRequest("POST" , server_url, null, postdata);
}

// User agent (ie browsers) detection functions
LLDL.isFirefox = function() { return (YAHOO.env.ua.gecko > 0);   } 
LLDL.isIE = function() { return (YAHOO.env.ua.ie > 0);  } 
LLDL.isOpera = function() { return (YAHOO.env.ua.opera) ; }
LLDL.isWebkit = function() { return (YAHOO.env.ua.webkit); }

//A cross-platform string to xml parser
LLDL.string2xml = function(string) {
    if(window.ActiveXObject){
        //IE
        var doc;
        if(document.implementation && document.implementation.createDocument) {
            //w3c standard
            doc = document.implementation.createDocument("", "", null);
        } else {
	        try{
	            //This is the IE way of doing it: first try the new XMLDOM plugin in ActiveX
	            doc = new window.ActiveXObject("MSXML2.DOMDocument");            
	        }catch(e) {//If not, defaults to the old one
	            doc = new window.ActiveXObject("Microsoft.XMLDOM");            
	        }
        }    
        doc.loadXML(string);
        return doc;
    } else if(window.DOMParser) {
        //Mozilla
        return (new DOMParser()).parseFromString(string, "text/xml");
    } else {
        //Safari: load the document as data via URL
        var url = "data:text/xml;charset=utf-8," + encodeURIComponent(string);
        var request = new XMLHttpRequest();
        request.open("GET", url, false);
        request.send(null);
        return request.responseXML;
    }
    alert('In LLDL.string2xml(): converting from string to xml failed due to parser not found!');
}    
LLDL.xml2string = function(xml_node) {
	if(window.ActiveXObject){
	   return xml_node.xml;	
	} else if(XMLSerializer) {
        //Mozilla related browsers
        return (new XMLSerializer()).serializeToString(xml_node);
    } 
//    else if(xml_node.xml) { 
//        // IE
//        //alert("xml_node.xml");
//        return xml_node.xml; 
//    } else if(xml_node.innerHTML) { 
//        // IE 
//        //alert("xml_node.innerHTML");
//        return xml_node.innerHTML; 
//    } else if(xml_node.outerHTML) { 
//        // IE 
//        //alert("xml_node.outerHTML");
//        return xml_node.outerHTML; 
//    } else {
//        return "Cannot convert xml node to string.";
//    }
} 
/** obj - JSON object; 
          format: {fn_on_click_ok: function, 
                   fn_on_click_cancel: function, 
                   scope: object, 
                   mask_img_url: url } 
*/
LLDL.login = function(obj) {
    function parseServiceNode(dom) {
    	var authen_node = dom.getElementsByTagName("authenticationNode");
    	if(!authen_node || !authen_node[0]) { return false; }
    	var service_node = authen_node[0].getElementsByTagName("service");
    	if(!service_node || !service_node[0]) { return false; }
    	return service_node[0];
    } 
    var wait_panel = LLDL.getWaitPanel(obj.mask_img_url, window.document.body);
	var handleOK = function(data) {//execution context: admin object
		var uname = LLDL.trim(data["s1.un"]); 
		var passwd = LLDL.trim(data["s1.pw"]); 
		var authen_url = "?o=xml&a=pr&s=Authentication&rt=r&ro=1&s1.asn=0&s1.uan=1&s1.aup=Login" +
		"&s1.umpw1=&s1.umpw2=&s1.umc=&s1.umgp=&s1.umun=&s1.umpw=&s1.umas=" +
		"&s1.un="+uname+"&s1.pw="+passwd;

	   wait_panel.show();
	   yuc.resetDefaultHeaders();
	   yuc.asyncRequest("GET", authen_url, {
    	  success:function(o){ 
			wait_panel.hide(); 
			var result = {username:'', password:'', role:'', login_error:null};
    		var service_node = parseServiceNode(o.responseXML);
    		
    		if(service_node) { 
        		var err = service_node.getAttribute("err");
        		if(err && err.length>0) {
        			if(err==="un-pw-err") { result.login_error = "Invalid username or password";  }
        			if(err==="as-false") { result.login_error = "Account disabled. See your administrator";}
        		}else{
        			result.username = service_node.getAttribute('un');
        			result.password = service_node.getAttribute('pw');
        			result.role = service_node.getAttribute('umgp');
                }
    		} else {
    		    result.login_error = 'Unknown reason';
    		}
    		obj.fn_on_click_ok.call(obj.scope, result);
          },				   
          failure:function(o){ 
        		wait_panel.hide(); 
        		alert(err_msg);
          }        
    	}, null);
		     
	};
	var handleCancel = function(){//execution context: admin object
	   if(obj.fn_on_click_cancel) {
	       obj.fn_on_click_cancel.call(obj.scope);
	   }  
    }; 
	var form_str = 
		"<table><tbody>" +
		"<tr><td><label for='s1.un'>Username:</label></td><td><input type='text' name='s1.un' value='' style='width:150px;'/></td></tr>" +
		"<tr><td><label for='s1.pw'>Password:</label></td><td><input type='password' name='s1.pw' value='' style='width:150px;'/></td></tr>" +
		"</tbody></table>";
    LLDL.promptData("Registered user login", form_str, handleOK, handleCancel, obj.scope);
}
/** 
   This function mimics window.prompt()
*  An example of dialog_form_el_str:
			"<form>" +
			"<table>" +
			"<tr><td><label for='username'>Username:</label></td><td><input type='text' name='username' value='' /></td></tr>" +
			"<tr><td><label for='passwd'>Password:</label></td><td><input type='password' name='passwd' value='' /></td></tr>" +
			"</table>" +
			"</form>";
   Note: the names (s1.un and s1.pw) will be used in ok_handler_fn to retrieve data from the returned object, 
        ie, data['username'] or data.username
*  @Return a JSON object (format: {name:value, name:value ...})
*/
LLDL.promptData = function(dialog_title, dialog_form_el_str, ok_handler_fn, cancel_handler_fn, handler_scope_obj, user_config) {
    if(!ok_handler_fn) { 
        alert('Invalid arguments passed to LLDL.promptData: missing ok_handler_fn'); return false; 
    }
	var handleOK = function(dummy_obj, key_listener_obj) {//scope: handler_scope_obj
		
		var data = LLDL.dialog.getData();
		if(data['_use_to_suppress_Enter_key_listener']) { delete data['_use_to_suppress_Enter_key_listener']; }
		var validation = true;
		for(var p in data) {
		    var user_value = data[p];
		    if(!user_value) { validation = false; break; }
		    if(typeof user_value !== 'string') {// a property of Object class? not what we want anyway. Skip! 
		        continue; 
		    }
		    user_value = LLDL.trim(user_value);
		    if(user_value.length < 1) {
		        alert('Empty input value!');
		        validation = false;
		    }
		    
		    // also varify that the user input data contain no malicious script or chacracters !
		    if(user_value.match(/<script>/)!=null || user_value.match(/<\/script>/) != null 
		       || user_value.match(/=/)!=null || user_value.match(/"/)!=null || user_value.match(/'/)!=null){
		        alert('Invalid input value: '+user_value);
		        validation = false;
		    }
		}
		if(validation) {
    		LLDL.dialog.destroy(); 
    		LLDL.dialog = null;
    		key_listener_obj = null;//delete the key listener object
    		ok_handler_fn.call(handler_scope_obj, data);
    		return true;  
		}
		return true;
    };
	var handleCancel = function(dummy_obj, key_listener_obj){//scope: handler_scope_obj
	
		LLDL.dialog.cancel(); 
		LLDL.dialog.destroy(); 
		LLDL.dialog = null;
		key_listener_obj = null;
		if(cancel_handler_fn) {
		  cancel_handler_fn.call(handler_scope_obj, null);
		}
		return true;
    }; 
    LLDL.initDialog(dialog_title, dialog_form_el_str, handleOK, handleCancel, handler_scope_obj, user_config);
};
LLDL.initDialog = function(dialog_title, dialog_form_el_str, ok_handler_fn/*fn*/, cancel_handler_fn/*fn*/, handler_scope_obj, user_config) {
    /*Note on what to pass to the value of 'handler' of buttons in LLDL.dialog:
      It can be simply a function name; or object of format: {  fn: Function, // The handler to call when the event fires.
                                                                obj: Object, // An object to pass back to the handler (eg, ok_handler_fn).
                                                                scope: Object // The object to use for the scope of the handler.
                                                             }
    */
	var dialog_c = LLDL.createEl("div", 'LLDL_dialog_c');
	dialog_c.innerHTML = "<div style='margin-bottom:20px;' id='dialog1_h' class='hd' >"+dialog_title+"</div>" +
	"<div class='bd'>" +
	"<form method='' action=''>" + dialog_form_el_str + 
	"<div style='visibility:hidden'><input type='text' name='_use_to_suppress_Enter_key_listener' size=1 value='dummy_value' /></div>" +
    "</form></div>"; 

//		// when the dialog is invoked within userlist_container (add/edit user form), 
//		//the blinking vertical bar cursor is missing in the text input fields
//		//Solution: lba.login_container cannot be set to 'display:none'; it must be either 'visibility:hidden' or removed from dom
	document.body.appendChild(dialog_c);
	// add Enter key listener on the 'Ok' button (the default button)
	var key_listener = LLDL.addKeyListener(dialog_c, 13, ok_handler_fn, handler_scope_obj);
	var passback_obj = null;//key_listener; 
	var ok_obj = {fn: ok_handler_fn, obj: passback_obj, scope: handler_scope_obj};
    var cancel_obj = {fn: cancel_handler_fn, obj: passback_obj, scope: handler_scope_obj};
	var cfg = user_config
	|| {   //width : "30em",
		fixedcenter : true,
		visible : false, 
		close: false,//no close button on the top right corner
		modal : true,//block the background
		underlay: "matte",
		draggable: false,
		constraintoviewport : true
	};
	cfg.buttons = [ { text:"OK", handler: ok_obj, isDefault: true },
	                { text:"Cancel", handler: cancel_obj, isDefault: false } ];
	LLDL.dialog = new YAHOO.widget.Dialog(dialog_c, cfg);
	LLDL.dialog.render();
	LLDL.dialog.show();	
	
//	yue.purgeElement(document.body, true);
};
LLDL.FLAXAlert = function(container) {
    var body = container || document.body;
    var flax_alert = LLDL.createInnerHTML('div','_FLAXAlert','<div class="hd">This is FLAX alert</div>', 'flax_alert_class');
    body.appendChild(flax_alert);
    var panel = new YAHOO.widget.Panel(flax_alert, {
        close:false,
        visible:true,
        draggable:false,
        fixedcenter:true,
        zindex:100,
        effect: {effect:YAHOO.widget.ContainerEffect.FADE, duration: 0.5 }
    });
    panel.render();
    panel.hide();
    yue.on(body, 'mousedown', function() {
        panel.hide();
    }, this, true);
    return panel;
};
LLDL.displayDialog = function(message, container){         
    var handleOK = function(){
         this.hide();
    } 
    var dialog_body = (container)? container : document.body;
    var dialog = new YAHOO.widget.SimpleDialog("simpledialog1",{ width: "400px",fixedcenter: true,modal:true,visible: true,draggable: false,close: true,text:message,icon: YAHOO.widget.SimpleDialog.ICON_INFO,constraintoviewport: true, buttons: [ { text: LLDL.bundle.activity_ok, handler:handleOK, isDefault:true }]} );                   
    dialog.render(dialog_body);
};
LLDL.displayNotice = function(elem, fn, scope_obj_of_fn) {
	if(!elem) return;
	this.toString = function(){return 'notice object';}
	ok_btn_id = '__lldlnotice_ok';
    var ok = function(){ 
        LLDL.notice.destroy(); 
        fn.call(scope_obj_of_fn);
    }
    var dummy_input = LLDL.createInnerHTML('div','',"<input type='button' id='"+ok_btn_id+"' value='OK'/>");
    yud.setStyle(dummy_input, 'text-align', 'right');
    elem.appendChild(dummy_input);
	document.body.appendChild(elem);
	LLDL.notice = new YAHOO.widget.Dialog(elem, 
            {   fixedcenter : true,
                visible : false, 
                close: false,//no close button on the top right corner
                modal : true,//block the background
                underlay: "matte",
                draggable: false,
                constraintoviewport : true
            });
    LLDL.notice.render();
    LLDL.notice.show();
    LLDL.notice.bringToTop();

//    var ok_btn = yud.get(ok_btn_id);
    LLDL.addKeyListener(ok_btn_id, 13, ok, this);
    yue.onAvailable(ok_btn_id, function(e) {
    	yud.get(ok_btn_id).focus();//Putting focus on the button is important to making the key listener work
    }, this);
    yue.on(ok_btn_id, 'click', ok, this, true);
}
LLDL.getwwwroot = function() {
    return window.location.protocol + '//' + window.location.host + '/greenstone3/';
}
/** Documentation on the argument "obj" passed into this function: 
 *  { caller_obj:'', // The object in whose scope callback_fn should run 
      callback_fn:'', //The fn to call at the timeout 
      passover_obj:'', // Optional - an object that is passed back to caller_fn from whoever inited this class
   }
 * */    
/** Function used to maintain a live connection to a server.
@param caller - Object, the obj by whom this class was instantiated
@param send_msg_fn - function object, the fn which is dedicated to sending msg to the server
@param interval (optional) - Integer, how frequent to send queries to the server
@param passover_data (optional) - a JSON obj which is used to pass data from whoever inited this class to send_msg_fn 
*/ 
LLDL.LiveConnection = function(caller, send_msg_fn, interval, passover_data) {
    var me = this;
    var time_interval = interval || 800;
    this.connect = function() {
        this.timer = window.setTimeout(function() {
            send_msg_fn.call(caller, passover_data);
			me.connect();
        }, time_interval);
    };
    this.disconnect = function() {
        window.clearTimeout(this.timer); 
    };
	return this;
}
/** Documentation on the argument "obj" passed into this function: 
 *  { caller_obj:'', // The object in whose scope callback_fn should run 
      callback_fn:'', // 
	  form:'',//html form object or the id of the form element or the name attribute value of the html form
      url:'', // url of the asyncRequest
	  
      passover_obj:'', // Optional - an object that is passed back to callback_fn from the fn which is calling this.talk2server
      show_loading_panel:'', // Optional - whether or not to show the "Loading ... Please wait" mask panel
      loading_img_url:'' // Optional - the url to the image used in the "Loading ... Please wait" mask panel /
   }
 * */    
LLDL.upload2server = function(obj) {
	if(!obj || !obj.form || !obj.url) return false;

    if(obj.show_loading_panel) {
        var wait_panel = LLDL.getWaitPanel(obj.loading_img_url, window.document.body);
    }
	
    // the second argument is true to indicate file upload.
	//When uploading files in applications over SSL and using IE, 
	//set the third argument to true to prevent IE from throwing domain security errors.
	// the third argument is set true to have Connection Manager
	// set the iframe source to "javascript:false".
	YAHOO.util.Connect.setForm(obj.form, true, true);
 
	var callback = {
		upload: function(o){}
	};
    if(obj.caller_obj && obj.callback_fn) {
        if(wait_panel) {
            wait_panel.show();
        }
		callback = {
		    upload:function(o){ 
                if(wait_panel) {
                    wait_panel.hide();
                } 
                var xml = LLDL.talk2serverGetResponseXML(o);
                obj.callback_fn.call(obj.caller_obj, xml, obj.passover_obj);                                                    
                
            }
		};    
    } 
	
    var cObj = YAHOO.util.Connect.asyncRequest('POST', obj.url, callback);
	return cObj;
};
LLDL.talk2server = function(obj) {
/** Documentation on the argument "obj" passed into this function:
 *  { caller_obj:'', // The object in whose scope callback_fn should run 
      callback_fn:'', // 
      http_method:'', // GET or POST
      url:'', // url of the asyncRequest
      err_msg:'', // Error message displayed in the 'failure:function()' of the handler
      passover_obj:'', // Optional - an object that is passed back to callback_fn from the fn which is calling this.talk2server
      postdata:'', // Optional - data when http_method is 'POST' 
      show_loading_panel:'', // Optional - whether or not to show the "Loading ... Please wait" mask panel
      loading_img_url:'' // Optional - the url to the image used in the "Loading ... Please wait" mask panel /
   }
 * */    
    if(obj.show_loading_panel) {
        var wait_panel = LLDL.getWaitPanel(obj.loading_img_url, window.document.body);
    }
    var postdata = '';
    var from_moodle = /ws_gateway\.php/.test(obj.url);
    if(from_moodle&&obj.postdata) {
    	// This talk2server request is going to be relayed by Moodle server.
        //Replace '=, &' with ';, ]]', so that in ws_gateway.php(Moodle) 'postdata' can be picked up by calling $_POST['request']
        // They will be replaced back in ws_gateway.php
        postdata = (obj.postdata).replace( new RegExp( "=", "g" ), LLDL.nv_separator );//replace '=' with ';;'
        postdata = postdata.replace( new RegExp( "&", "g" ), LLDL.arg_separator );//replace '&' with ']]'
        postdata = 'request=' + encodeURIComponent(postdata);  

    } else {
    	postdata = obj.postdata;
    }

    var http_method = obj.http_method || 'GET';
    if(obj.caller_obj && obj.callback_fn) {
        if(wait_panel) {
            wait_panel.show();
        }
//        yuc.resetDefaultHeaders();
        yuc.initHeader("Cache-Control", "no-cache", true); 
        yuc.initHeader("Expires", "-1", true); 
//        yuc.initHeader("Content-Type", "text/xml", true); 

        yuc.asyncRequest(http_method, obj.url, {
            success:function(o){ 
                if(wait_panel) {
                    wait_panel.hide();
                } 
                var xml = LLDL.talk2serverGetResponseXML(o);//(from_moodle)? LLDL.string2xml(o.reponseText) : o.responseXML;
                obj.callback_fn.call(obj.caller_obj, xml, obj.passover_obj);                                                    
//                obj.callback_fn.call(obj.caller_obj, LLDL.string2xml(o.reponseText), obj.passover_obj);                                                    
                
            },                 
            failure:function(o){ 
                if(wait_panel) {
                    wait_panel.hide();
                } 
                var xml = LLDL.createEl('err', '', obj.err_msg);
                obj.callback_fn.call(obj.caller_obj, xml, obj.passover_obj);
                console.log('talk2server failed: ' + obj.err_msg + " url=" + obj.url + "*" + "postdata="+obj.postdata+"*");
            }
        }, postdata);    
    } else {
        yuc.asyncRequest(http_method, obj.url, null, postdata);
    }
};
LLDL.talk2serverGetResponseXML = function(o) {
/**
window.document.write(LLDL.getObjProperty(o));
window.document.write('----------'+LLDL.xml2string(o.responsXML));
 **/    
    var xml = "Error getting response xml in LLDL.talk2server.getResponseXML";
    if(o.responseXML && o.responseXML.getElementsByTagName('response')[0]) {
    	//Somehow, even responseXML is empty, it still evals true. Hence the checking its 'response' child element
    	//This fixes a bug in IE8
        xml = o.responseXML;
    } else if(o.responseText && o.responseText.length>0) {
        //cannot use o.responseXML when ajax requests are replayed by php server, whats being sent back from php is string literal
        xml = LLDL.string2xml(o.responseText);
    }
    return xml;
};
LLDL.getWaitPanel = function(loading_img_src, panel_body) {
    var wait = 
            new YAHOO.widget.Panel("_LLDL_wait_panel_",  { width: "300px", 
                                              fixedcenter: true, 
                                              close: false, 
                                              draggable: false, 
//                                              zindex:4,
                                              modal: true,
                                              visible: false,
                							  underlay: "matte",
                							  constraintoviewport : true
                                            } 
                                        );

    var img_src = loading_img_src || '../../images/blocking_loading.gif';    
    wait.setBody("<p style='text-align:center;'>Loading, please wait ...</p>");
    wait.setHeader("<img width=100% src=\""+img_src+"\"/>");
    wait.render((panel_body)?panel_body:window.document.body); 
    return wait;     
};
LLDL.cloneObject = function(o) {
	for (p in o) {
		if(typeof o[p] == 'object') {
			this[p] = new cloneObject(o[p]);
		} else {
			this[p] = o[p];
		}
	}
	return this;
}
LLDL.getObjProperty = function(obj) {
    if(typeof obj !== 'object') { alert("in LLDL.dumpProperty: obj is not an object"); return; }
    var v = '';
    for (var p in obj) {
        v += p + ": " + obj[p] + "]  ";
    }
    return v;
}
LLDL.dumpDom = function(xml, level) {
	var dumped_text = "";
	if(!level) level = 0;
	
	//The padding given at the beginning of the line.
	var level_padding = "";
	for(var j=0;j<level;j++) level_padding += ' ' +level + "  ";
	
	if(!xml || xml.nodeType !== 1) { return false; }
	var children = xml.childNodes; 
	var len = children.length;
    dumped_text += level_padding + xml.tagName + ' ';
    for(var i=0; i<len; i++) {
        var c = children[i];
        if(c.nodeType !== 1) continue;
        dumped_text += LLDL.dumpDom(c, level+1);
    }    
    return dumped_text;
}
LLDL.dumpArray = function(arr, level) {
	var dumped_text = "";
	if(!level) level = 0;
	
	//The padding given at the beginning of the line.
	var level_padding = "";
	for(var j=0;j<level+1;j++) level_padding += "    ";
	
	if(typeof(arr) == 'object') { //Array/Hashes/Objects 
		for(var item in arr) {
			var value = arr[item];
			
			if(typeof(value) == 'object') { //If it is an array,
				dumped_text += level_padding + "'" + item + "' ...\n";
				dumped_text += dump(value,level+1);
			} else {
				dumped_text += level_padding + "'" + item + "' => \"" + value + "\"\n";
			}
		}
	} else { //Stings/Chars/Numbers etc.
		dumped_text = "===>"+arr+"<===("+typeof(arr)+")";
	}
	return dumped_text;
}

/* An example: the int value 125 would be converted to [125,120,110,100,90,80,70,60,50,40,30,20,10,9,8,7,6,5,4,3,2,1]
* @param number - int
* @return Array of integers
*/
LLDL.samplingNumber = function(number) {
        var num = number;
        if(typeof num !== "number") {
            num = parseInt(num);
        }
        var arr = [];
        if(num <= 0) return arr;
        if(num <= 11) {
            for(var i=num; i>=1; i--) {
                arr.push(""+i);
            }
            return arr;
        }
        
        //the number 'num' is greater than 11, use the following algorithm
        arr.push(""+num);
        if((num % 10) === 0) {
            num = num - 1; // This is to prevent the same exact number from being added again below
        }
        var str_value = num.toString();
        var digit_arr = str_value.split("");
        var len = digit_arr.length; 
        for(var i=0; i<len-1; i++) {
            //parse each digit to int
            var digit = (i===0)? parseInt(digit_arr[i], 10) : 9;
            //work out the coefficient to multiply the digit
            var coeff_digit = (len - 1) - i;
            var coefficient = 1;
            for(var c=0; c<coeff_digit; c++) {
                coefficient *= 10;
            }
            for(var k=digit; k>0; k--) {
                var digit_step = k*coefficient;
                arr.push(""+digit_step);
            }
        }
        //for the last digit, print out 1 - 10
        for(var i=9; i>=1; i--) {
            arr.push(""+i);
        }
        return arr;            
}
////////////////////////////////////////////////
/*The following Queue Object is an implementation of the common queue data structure in javascript.
*/
function Queue(size) {
    this.size = (!size)? 50 : size;
    this.queue = [];
    
}
Queue.prototype = {
    /** Add an item at the end of the queue. Adjust the length of the queue according to the specification of the queue size.
    */
    enqueue: function(item) {
        this.queue.push(item);
        if(this.queue.length > this.size) {
            this.queue.shift();//shift method affects the length of the array it is operating on
        }
    },
    /** Removes and returns the first element of the queue. Reduce the length of the queue by 1.
    */
    dequeue: function() {
        return this.queue.shift();
    },
    /** Returns the element of the queue at the specified index. If the specified index is out of range, null is returned.
    *   This method does not remove the element from the queue, nor affect length of the queue.
    */
    getAt: function(index) {
        if(index < 0 || index > this.queue.length - 1) {
            return null;
        }
        return this.queue[index];
    },
    isEmpty: function() {
        return this.queue.length == 0;
    },
    clear: function() {
        for(var i=0; i<this.queue.length; i++) {
            this.queue.shift();
        }
    }
}
/*The following Stack Object is an implementation of the common stack data structure in javascript.
*/
function Stack() {
    this.stack = [];
    this.length = this.getLength;
}
Stack.prototype = {
    /** Push an item onto stack. 
    */
    push: function(item) {
        this.stack.unshift(item);//unshift method increase the length of the array it is operating on
    },
    /** Removes and returns the first element of the stack. Reduce the length of the stack by 1.
    */
    pop: function() {
        return this.stack.shift();
    },
    /** Returns the element of the stack at the specified index. If the specified index is out of range, null is returned.
    *   This method does not remove the element from the stack, nor affect length of the stack.
    */
    getAt: function(index) {
        if(index < 0 || index > this.stack.length - 1) {
            return null;
        }
        return this.stack[index];
    },
    isEmpty: function() {
        return this.stack.length == 0;
    },
    clear: function() {
        for(var i=0; i<this.stack.length; i++) {
            this.stack.shift();
        }
    },
    getLength: function() {
        return this.stack.length;
    }
}
/*The following HashMap Object is an implementation of HashMap in java for javascript.
  It is modified based on a version from http://freecode-freecode.blogspot.com/2007/06/hashmap-object-in-javascript-like.html
*/
function HashMap(){
    // members
    this.keyArray = new Array(); // Keys
    this.valArray = new Array(); // Values
        
    // methods
    this.put = put;
    this.get = get;
    this.getKey = getKey;
    this.getValue = getValue;
    this.size = size;  
    this.clear = clear;
    this.keySet = keySet;
    this.valSet = valSet;
    this.indexOf = indexOf;
    this.containsKey = containsKey;
    this.remove = remove;

    function put( key, val ){
        var elementIndex = this.indexOf( key );
        
        if( elementIndex == (-1) )
        {
            this.keyArray.push( key );
            this.valArray.push( val );
        }
        else
        {
            this.valArray[ elementIndex ] = val;
        }
    }
    
    function get( key ){
        var result = null;
        var elementIndex = this.indexOf( key );
    
        if( elementIndex != (-1) )
        {   
            result = this.valArray[ elementIndex ];
        }  
        
        return result;
    }
    
    function getKey( idx ){
    	return this.keyArray[idx];
    }
    function getValue( idx ){
    	return this.valArray[idx];
    }
    
    function remove( key ){
        var elementIndex = this.indexOf( key );
    
        if( elementIndex != (-1) ) {
            this.keyArray = this.keyArray.removeAt(elementIndex);
            this.valArray = this.valArray.removeAt(elementIndex);
            return true;
        }  
        
        return false;
    }
    
    function size(){
        return (this.keyArray.length);  
    }
    
    function clear(){
        for( var i = 0; i < this.keyArray.length; i++ )
        {
            this.keyArray.pop(); this.valArray.pop();   
        }
    }
    
    function keySet(){
        return (this.keyArray);
    }
    
    function valSet(){
        return (this.valArray);   
    }
    
    function toString(){
        var result = "";
        
        for( var i = 0; i < this.keyArray.length; i++ )
        {
            result += "Key: " + this.keyArray[ i ] + "\tValues: " + this.valArray[ i ] + "\n";
        }
        return result;
    }
    function containsKey( key ) {
        if( !key ) return false;
        for( var i = 0; i < this.keyArray.length; i++ ){
            if( this.keyArray[ i ] == key ) {
                return true;
            }
        }
        return false;   
    }
    function indexOf( key ){
        var result = (-1);
    
        for( var i = 0; i < this.keyArray.length; i++ )
        {
            if( this.keyArray[ i ] == key )
            {
                result = i;
                break;
            }
        }
        return result;
    }
    
    function removeAt( index ){
      var part1 = this.slice( 0, index);
      var part2 = this.slice( index+1 );
    
      return( part1.concat( part2 ) );
    }
    Array.prototype.removeAt = removeAt;
}//End of class HashMap()
String.prototype.trim = function() {
	return this.split(/\s/).join(' ');
}
String.prototype.convertHTMLEntities = function() {
	return this.replace(/\&amp;/g, '&').
	            replace(/\&lt;/g, '<').
	            replace(/\&gt;/g, '>').
	            replace(/\&spos;/g, "'").
	            replace(/\&quot;/g, '"');
}
/*StringBuffer Object - an implementation of StringBuffer in java for javascript.
*/
function StringBuffer(){
  this.buffer = [];
}
StringBuffer.prototype.append = function(string) { 
  this.buffer.push(string); 
  return this; 
};
StringBuffer.prototype.toString = function(){ 
  return this.buffer.join(""); 
};/////End of class StringBuffer

/* Customised Array functions
*/
//The prototype functions (indexOf, lastIndexOf, every, filter, forEach, map, and some) are provided by the Mozilla foundation and
//is distributed under the MIT license.
//http://www.ibiblio.org/pub/Linux/LICENSES/mit.license

if (!Array.prototype.indexOf)
{
  Array.prototype.indexOf = function(elt /*, from*/)
  {
    var len = this.length;

    var from = Number(arguments[1]) || 0;
    from = (from < 0)
         ? Math.ceil(from)
         : Math.floor(from);
    if (from < 0)
      from += len;

    for (; from < len; from++)
    {
      if (from in this &&
          this[from] === elt)
        return from;
    }
    return -1;
  };
}
if (!Array.prototype.lastIndexOf)
{
  Array.prototype.lastIndexOf = function(elt /*, from*/)
  {
    var len = this.length;

    var from = Number(arguments[1]);
    if (isNaN(from))
    {
      from = len - 1;
    }
    else
    {
      from = (from < 0)
           ? Math.ceil(from)
           : Math.floor(from);
      if (from < 0)
        from += len;
      else if (from >= len)
        from = len - 1;
    }

    for (; from > -1; from--)
    {
      if (from in this &&
          this[from] === elt)
        return from;
    }
    return -1;
  };
}
/**
 * The every method is a Firefox method which accepts a function as an argument. Every value of the array is passed to that function until the function returns false. If no elements return false then every will return true, if an element returned false then every will return false. It's a convenient way to test an Array and see if every element is a number for instance.

This method will pass the current value, the current index, and a pointer to the array to your function. myfunction(curValue, curIndex, curArray)

var isNumeric = function(x) {
   // returns true if x is numeric and false if it is not.
   var RegExp = /^(-)?(\d*)(\.?)(\d*)$/; 
   return String(x).match(RegExp);
}
var myArray = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];

document.writeln(myArray.every(isNumeric));   // outputs: true

var myArray = [1,2,3,4,5,6,7,8,9,'ten',11,12,13,14,15];

document.writeln(myArray.every(isNumeric));   // outputs: false
 */
if (!Array.prototype.every){
  Array.prototype.every = function(fun /*, thisp*/)
  {
    var len = this.length;
    if (typeof fun != "function")
      throw new TypeError();

    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in this &&
          !fun.call(thisp, this[i], i, this))
        return false;
    }

    return true;
  };
}
/**
 * Filter creates a new Array of items which evaluate to true in the supplied function. In the Array.every() method, we tested if the entire Array was composed of Numbers. In Array.filter() we can extract all the numbers, creating a new Array in the process.

This method will pass the current value, the current index, and a pointer to the array to your function. myfunction(curValue, curIndex, curArray)

Here we pass the array through the same function as .every() -- isNumeric -- and if the element is a number it's placed in the new "oddArray" Array.

var isNumeric = function(x) {
   // returns true if x is numeric and false if it is not.
   var RegExp = /^(-)?(\d*)(\.?)(\d*)$/; 
   return String(x).match(RegExp);
}
var myArray = [1,'two',3,'four',5,'six',7,'eight',9,'ten'];
var oddArray=myArray.filter(isNumeric);

document.writeln(oddArray);   // outputs: 1,3,5,7,9
 */
if (!Array.prototype.filter)
{
  Array.prototype.filter = function(fun /*, thisp*/)
  {
    var len = this.length;
    if (typeof fun != "function")
      throw new TypeError();

    var res = new Array();
    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in this)
      {
        var val = this[i]; // in case fun mutates this
        if (fun.call(thisp, val, i, this))
          res.push(val);
      }
    }

    return res;
  };
}
/**
 * This is an odd little method. All it does is pass each element of the Array to the passed function. It ignores any results from the function and it returns nothing itself. It will pass all the Array contents through the function of your choice but the Array itself will not be affected and it will return nothing by itself.

This method will pass the current value, the current index, and a pointer to the array to your function. myfunction(curValue, curIndex, curArray)

var printArray = function (x, idx) {
   document.writeln('['+idx+'] = '+x);
}

var myArray = [1,'two',3,'four',5];

myArray.forEach(printArray); // outputs: [0] = 1 [1] = two [2] = 3 [3] = four [4] = 5
 */
if (!Array.prototype.forEach)
{
  Array.prototype.forEach = function(fun /*, thisp*/)
  {
    var len = this.length;
    if (typeof fun != "function")
      throw new TypeError();

    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in this)
        fun.call(thisp, this[i], i, this);
    }
  };
}
/**
 * The map method will call the provided function for each value of the array and it will return an array containing the results of those function calls.

The callback function is called with three arguments: the value, the index, and a pointer to the array being used respectively.

In the following example each element of the array is tested to see if it is numeric, if it is, it's passed into the new array, otherwise a zero is inserted.

var isNumeric = function(x) {
   // returns true if x is numeric and false if it is not.
   var RegExp = /^(-)?(\d*)(\.?)(\d*)$/; 
   return String(x).match(RegExp);
}
var testElement = function(x) {
   if (isNumeric(x)) {
      return x;
   } else {
      return 0;
   }
}

var myArray = [1,'two',3,'four',5,'six',7,'eight',9,'ten'];
var newArray= myArray.map(testElement);
document.writeln(newArray); // outputs: 1,0,3,0,5,0,7,0,9,0
 */
if (!Array.prototype.map)
{
  Array.prototype.map = function(fun /*, thisp*/)
  {
    var len = this.length;
    if (typeof fun != "function")
      throw new TypeError();

    var res = new Array(len);
    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in this)
        res[i] = fun.call(thisp, this[i], i, this);
    }

    return res;
  };
}
/**
 * The some() method will pass each element of the Array through the supplied function until true has been returned. If the function returns true some will in turn return true. If the entire array has been traversed and no true condition was found then some() will return false.

var isNumeric = function(x) {
   // returns true if x is numeric and false if it is not.
   var RegExp = /^(-)?(\d*)(\.?)(\d*)$/; 
   return String(x).match(RegExp);
}
var myArray = ['one', 'two', 'three', 'four', 'five'];

document.writeln(myArray.some(isNumeric));   // outputs: false

var myArray = ['one', 'two', 3, 'four', 'five'];

document.writeln(myArray.some(isNumeric));   // outputs: true
 */
if (!Array.prototype.some)
{
  Array.prototype.some = function(fun /*, thisp*/)
  {
    var len = this.length;
    if (typeof fun != "function")
      throw new TypeError();

    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in this &&
          fun.call(thisp, this[i], i, this))
        return true;
    }

    return false;
  };
}
/** Original code source: http://www.hunlock.com/blogs/mastering_javascript_arrays
 * var tmp = [5,9,12,18,56,1,10,42,'blue',30, 7,97,53,33,30,35,27,30,'35','Ball', 'bubble'];
              0/1/2 /3 /4/5 /6 /7     /8  /9/10/11/12/13/14/15/16/17/  18/    19/      20
var thirty=tmp.find(30);             // Returns 9, 14, 17
var thirtyfive=tmp.find('35');       // Returns 18
var thirtyfive=tmp.find(35);         // Returns 15
var haveBlue=tmp.find('blue');       // Returns 8
var notFound=tmp.find('not there!'); // Returns false
var regexp1=tmp.find(/^b/);          // returns 8,20    (first letter starts with b)
var regexp1=tmp.find(/^b/i);         // returns 8,19,20 (same as above but ignore case)
 */
Array.prototype.findIndex = function(searchStr) {
  var returnArray = false;
  for (i=0; i<this.length; i++) {
    if (typeof(searchStr) == 'function') {
      if (searchStr.test(this[i])) {
        if (!returnArray) { returnArray = [] }
        returnArray.push(i);
      }
    } else {
      if (this[i]===searchStr) {
        if (!returnArray) { returnArray = [] }
        returnArray.push(i);
      }
    }
  }
  return returnArray;
}
Array.prototype.findValue = function(searchStr) {
  var returnArray = false;
  for (i=0; i<this.length; i++) {
    if (typeof(searchStr) == 'function') {
      if (searchStr.test(this[i])) {
        if (!returnArray) { returnArray = [] }
        returnArray.push(this[i]);
      }
    } else {
      if (this[i]===searchStr) {
        if (!returnArray) { returnArray = [] }
        returnArray.push(this[i]);
      }
    }
  }
  return returnArray;
}
Array.prototype.sortString = function(ascending) {
    var arr = (ascending)? [1, -1] : [-1, 1];
    this.sort(function(a, b) {
           if(a > b) { return arr[0]; }
           if(a == b) { return 0; }
           return arr[1]; 
    });
};
Array.prototype.sortNumber = function(ascending) {
    this.sort(function(a, b){ return (ascending)? (a - b) : (b - a); });
};
//Array.prototype.numberFind = function(val/*value to find(int value)*/, return_an_index_to_insert/*boolean*/){
//    var h = this.length, l = -1, m;
//    while(h - l > 1)
//        if(this[m = h + l >> 1] < val) l = m;
//        else h = m;
//    return this[h] != val ? return_an_index_to_insert ? h : -1 : h;
//};
Array.prototype.binIndexOf = function(val/*value to find*/){
//    NOTE: This method only applies to ordered array !!!
//          For unordered array, use indexOf().
    var h = this.length, l = -1, m;
    while(h - l > 1)
        if(this[m = h + l >> 1] < val) l = m;
        else h = m;
    return this[h] != val ? -1 : h;
};

//Array.prototype.binIndexOf = function(val) {
///** Use binary search to find the index of an element of an ordered array.
//    NOTE: This method only applies to ordered array !!!
//          For unordered array, use indexOf().
//*/
//    if(!this || this.length < 1) {
//        return -1;
//    }
//    var left = 0, right = this.length - 1;
//    while (left <= right) {    
//        var mid = parseInt((left + right)/2);        
//        if (this[mid] == val) {        
//            return mid;
//        } else if (this[mid] < val) {
//            left = mid + 1;
//        } else {        
//            right = mid - 1;
//        }
//    }
//    return -1;
//};
Array.prototype.removeAt = function (index) {
  //Note on how to use this function: array_name = array_name.removeAt(index);  
  if(index < 0 || index > this.length - 1) {
      return this;
  }  
  if(index == 0) {
      return this.slice(1);
  }
  if(index == this.length - 1) {
      return this.slice( 0, index);
  }
  var part1 = this.slice( 0, index);//exclude the element at index 'index'  
  var part2 = this.slice( index+1 );
  return( part1.concat( part2 ) );
  
  throw "Error removing element from array";
};
//Array.prototype.del = function(e) {
//	var idx = this.indexOf(e);
//	if(idx==-1) return;
//	this.splice(idx, 1);
//}    
Array.prototype.remove = function(elem) {
	//Note on how to use this function: array_name = array_name.remove(array_element);  
  return this.removeAt(this.indexOf(elem));
};
Array.prototype.swap = function(index1, index2) {
	var temp = this[index1];
	this[index1] = this[index2];
	this[index2] = temp;
	return this;
};
//if(!Array.prototype.indexOf) {
//	/**
//	elt - Element to locate in the array. 
//	from - The index at which to begin the search. Defaults to 0, i.e. the whole array will be searched. 
//	    If the index is greater than or equal to the length of the array, -1 is returned, i.e. the array will not be searched. 
//	    If negative, it is taken as the offset from the end of the array. 
//	    Note that even when the index is negative, the array is still searched from front to back. 
//	    If the calculated index is less than 0, the whole array will be searched. 
//	*/
//	Array.prototype.indexOf = function(elt /*, from*/)  {
//	    var len = this.length >>> 0;
//	
//	    var from = Number(arguments[1]) || 0;
//	    from = (from < 0)
//	         ? Math.ceil(from)
//	         : Math.floor(from);
//	    if (from < 0)
//	      from += len;
//	
//	    for (; from < len; from++)
//	    {
//	      if (from in this &&
//	          this[from] === elt)
//	        return from;
//	    }
//	    return -1;
//	};
//}
//Array.prototype.indexOf = function(elem) {
//	for (var i=0; i<this.length; i++) {
//		if (elem == this[i]) {
//			return i;
//		}
//	}
//	return -1;
//};
Array.prototype.shuffle = function()
{
  for (var i = 0; i < this.length; i++)
  {
    // Random item in this array.
    var r = parseInt(Math.random() * this.length);
    var obj = this[r];
 
    // Swap.
    this[r] = this[i];
    this[i] = obj;
  }
};  
Array.prototype.append = function(arr) {
	if(!arr || arr.length<1) return;
	for(var i=0; i<arr.length; i++) {
		this.push(arr[i]);
	}
}
Array.prototype.complement = function(subset_of_arr){
	var complement_arr = subset_of_arr.filter(function(e) { return this.indexOf(e) < 0 });
	return comlement_arr;
}
//Object.prototype.clone = function() {
//  var newObj = (this instanceof Array) ? [] : {};
//  for (i in this) {
//    if (i == 'clone') continue;
//    if (this[i] && typeof this[i] == "object") {
//      newObj[i] = this[i].clone();
//    } else newObj[i] = this[i]
//  } return newObj;
//};                  
//////////////////////////
/**
 * @param time - string or integer (number of seconds)
 * @return array of integers - [hours, minutes, seconds]
 */
LLDL.parseTimeInSeconds = function(time) {
	
    var time_in_second = (typeof time!='number')?parseInt(time) : time;
    var hours = parseInt(time_in_second/3600, 10);//rounded to decimal point
    var remainder = time_in_second%3600;
    var minutes = parseInt(remainder/60, 10);
    var seconds = remainder%60;
    return [hours, minutes, seconds];
}
/**Countdown digital clock
 * @param time String denotes to number of seconds
 * @param cfg {caller_obj:obj, timeout_fn:fn to call at the end of the countdown, data:data to be passed to end_fn} */
LLDL.CountdownClock = function(time, cfg) {
	this.setTime(time);
	this.cfg = cfg;
	this.getBody();
};
LLDL.CountdownClock.prototype = {
	setTime: function(time) {
		var t_arr = LLDL.parseTimeInSeconds(time);
		this.hours = t_arr[0];
        this.minutes = t_arr[1];
        this.seconds = t_arr[2];
	},
	getBody: function() {
        this.body = LLDL.createEl('div', '','','_LLDL_countdownclock');
        this.drawClock();
	},
	drawClock: function() {
		this.body.innerHTML = this.formatIt(this.hours)+':'+this.formatIt(this.minutes)+':'+this.formatIt(this.seconds);
	},
	formatIt: function(t) {
		if(t < 10) { return	'<span>0'+t+'</span>'; }
		return '<span>'+t+'</span>';
	},
	updateClock: function() {
		//In the order of seconds->minutes->hours, find the first item that hasn't reached zero and decrement it,
		// and reset items below it (if any) to 59.
		if(this.seconds == 0) {
			if(this.minutes == 0){
				if(this.hours == 0) {
					this.timeOut(); return;
				} else {
    				this.hours--; this.minutes = 59; this.seconds = 59;
				}
			} else {
				this.minutes--; this.seconds = 59;
			}
		} else {
			this.seconds--;
		}
		this.drawClock();
		this.startClock();
	},
	//This function is called by whoever instantiated this class
	startClock: function() {
        var context_obj = this;
        this.timer = window.setTimeout(function() {
            context_obj.updateClock.call(context_obj);
        }, 1000);
	},
	stopClock: function() {
		var timer = this.timer;
		window.clearTimeout(timer);
	},
	timeOut: function() {
        this.cfg.timeout_fn.call(this.cfg.caller_obj, this.cfg.data);
	}
}
/*******************************************************
COOKIE FUNCTIONALITY
Based on "Night of the Living Cookie" by Bill Dortch
(c) 2003, Ryan Parman
Code source: http://xref.moodle.org/nav.html?lib/cookies.js.source.html
Distributed according to SkyGPL 2.1, http://www.skyzyx.com/license/
*******************************************************/
function Cookie(name, value, duration, path, domain, secure)
{
        // Passed Values
        this.name=name;
        if(value)this.value=value;
        if(duration){
           this.duration=duration;
          }
        else{
           this.duration=3; // default 3 days
         }

        if(path)this.path=path;
        if(domain)this.domain=domain;
        if(secure)this.secure=secure;

	// Read cookie
	this.read=function()
	{
		// To allow for faster parsing
		var ck=document.cookie;

		var arg = this.name + "=";
		var alen = arg.length;
		var clen = ck.length;
		var i = 0;

		while (i < clen)
		{
			var j = i + alen;
			if (ck.substring(i, j) == arg)
			{
				var endstr = ck.indexOf (";", j);
				if (endstr == -1) endstr = ck.length;
				return unescape(ck.substring(j, endstr));
			}
			i = ck.indexOf(" ", i) + 1;
			if (i == 0) break;
		}
		return null;
	}

	// Set cookie
	this.set=function()
	{
                exp = new Date();
                base = new Date(0);
                skew = base.getTime();
                if (skew > 0)  exp.setTime (exp.getTime() - skew);
                exp.setTime(exp.getTime() + this.duration*24*60*60*1000);

                document.cookie = this.name + "=" + escape (this.value) +
                                ((this.expires) ? "; expires=" + exp.toGMTString() : "") +
                                ((this.path) ? "; path=" + this.path : "") +
                                ((this.domain) ? "; domain=" + this.domain : "") +
                                ((this.secure) ? "; secure" : "");

	}

	// Kill cookie
	this.kill=function()
	{
		document.cookie = this.name + "=" +
				((this.path) ? "; path=" + this.path : "") +
				((this.domain) ? "; domain=" + this.domain : "") +
				"; expires=Thu, 01-Jan-70 00:00:01 GMT";
	}

	// Change cookie settings.
	this.changeName=function(chName) { this.kill(); this.name=chName; this.set(); }
	this.changeVal=function(chVal) { this.kill(); this.value=chVal; this.set(); }
	this.changeExp=function(chExp) { this.kill(); this.expires=chExp; this.set(); }
	this.changePath=function(chPath) { this.kill(); this.path=chPath; this.set(); }
	this.changeDomain=function(chDom) { this.kill(); this.domain=chDom; this.set(); }
	this.changeSecurity=function(chSec) { this.kill(); this.secure=chSec; this.set(); }
}
/*
LLDL.setCookie = function( cookie_name, cookie_value, lifespan_in_days, valid_domain ){
    // http://www.thesitewizard.com/javascripts/cookies.shtml
    var domain_string = valid_domain ?
                       ("; domain=" + valid_domain) : '' ;
    document.cookie = cookie_name +
                       "=" + encodeURIComponent( cookie_value ) +
                       "; max-age=" + 60 * 60 *
                       24 * lifespan_in_days +
                       "; path=/" + domain_string ;
};
LLDL.getCookie = function( cookie_name ){
    // http://www.thesitewizard.com/javascripts/cookies.shtml
    var cookie_string = document.cookie ;
    if (cookie_string.length != 0) {
        var cookie_value = cookie_string.match (
                        '(^|;)[\s]*' +
                        cookie_name +
                        '=([^;]*)' );
        if(cookie_value && cookie_value[2]) {                
            return decodeURIComponent ( cookie_value[2] ) ;
        }
    }
    return null ;
};
LLDL.deleteCookie = function( cookie_name, valid_domain ){
    // http://www.thesitewizard.com/javascripts/cookies.shtml
    var domain_string = valid_domain ?
                       ("; domain=" + valid_domain) : '' ;
    document.cookie = cookie_name +
                       "=; max-age=0; path=/" + domain_string ;
};*/
