/**
 * This file contains helper methods for the olatcore web app framework and the
 * learning management system OLAT
 */

/** OpenOLAT namespace **/
OPOL = {};

//used to mark form dirty and warn user to save first.
var o2c=0;
var o3c=new Array();//array holds flexi.form id's
// o_info is a global object that contains global variables
o_info.guibusy = false;
o_info.linkbusy = false;
o_info.scrolling = false;
//debug flag for this file, to enable debugging to the olat.log set JavaScriptTracingController to level debug
o_info.debug = true;
// o_info.drake is supervised and linked to .o_drake DOM element
o_info.drakes = new Array();

/**
 * The BLoader object can be used to :
 * - dynamically load and unload CSS files
 * - dynamically load JS files
 * - execute javascript code in a global context, meaning on window level
 *
 * 03.04.2009 gnaegi@frentix.com 
 */
var BLoader = {
	// List of js files loaded via AJAX call.
	_ajaxLoadedJS : new Array(),
		
	// Internal mehod to check if a JS file has already been loaded on the page
	_isAlreadyLoadedJS: function(jsURL) {
		var notLoaded = true;
		// first check for scrips loaded via HTML head
		jQuery('head script[src]').each(function(s,t) {
			if (jQuery(t).attr('src').indexOf(jsURL) != -1) {
				notLoaded = false;
			}
		});
		// second check for script loaded via ajax call
		if (jQuery.inArray(jsURL, this._ajaxLoadedJS) != -1) notLoaded = false;
		return !notLoaded;
	},
		
	// Load a JS file from an absolute or relative URL by using the given encoding. The last flag indicates if 
	// the script should be loaded using an ajax call (recommended) or by adding a script tag to the document 
	// head. Note that by using the script tag the JS script will be loaded asynchronous 
	loadJS : function(jsURL, encoding, useSynchronousAjaxRequest) {
		if (!this._isAlreadyLoadedJS(jsURL)) {		
			if (o_info.debug) o_log("BLoader::loadJS: loading ajax::" + useSynchronousAjaxRequest + " url::" + jsURL);
			if (useSynchronousAjaxRequest) {
				jQuery.ajax(jsURL, {
					async: false,
					dataType: 'script',
					cache: true,
					success: function(script, textStatus, jqXHR) {
						//BLoader.executeGlobalJS(script, 'loadJS');
					}
				});
				this._ajaxLoadedJS.push(jsURL);
			} else {
				jQuery.getScript(jsURL);			
			}
			if (o_info.debug) o_log("BLoader::loadJS: loading DONE url::" + jsURL);
		} else {
			if (o_info.debug) o_log("BLoader::loadJS: already loaded url::" + jsURL);			
		}
	},

	// Execute the given string as java script code in a global context. The contextDesc is a string that can be 
	// used to describe execution context verbally, this is only used to improve meaninfull logging
	executeGlobalJS : function(jsString, contextDesc) {
		try{
			// FIXME:FG refactor as soon as global exec available in prototype
			// https://prototype.lighthouseapp.com/projects/8886/tickets/433-provide-an-eval-that-works-in-global-scope 
			if (window.execScript) window.execScript(jsString); // IE style
			else window.eval(jsString);
		} catch(e){
			if(window.console) console.log(contextDesc, 'cannot execute js', jsString);
			if (o_info.debug) { // add webbrowser console log
				o_logerr('BLoader::executeGlobalJS: Error when executing JS code in contextDesc::' + contextDesc + ' error::"'+showerror(e)+' for: '+escape(jsString));
			}
			// Parsing of JS script can fail in IE for unknown reasons (e.g. tinymce gets 8002010 error)
			// Try to do a 'full page refresh' and load everything via page header, this normally works
			if (window.location.href.indexOf('o_winrndo') != -1) window.location.reload();
			else window.location.href = window.location.href + (window.location.href.indexOf('?') != -1 ? '&' : '?' ) + 'o_winrndo=1';
		}		
	},
	
	// Load a CSS file from the given URL. The linkid represents the DOM id that is used to identify this CSS file
	loadCSS : function (cssURL, linkid, loadAfterTheme) {
		var doc = window.document;
		try {
			if(doc.createStyleSheet) { // IE
				// double check: server side should do so, but to make sure that we don't have duplicate styles
				var sheets = doc.styleSheets;
				var cnt = 0;
				var pos = 0;
				for (i = 0; i < sheets.length; i++) {
					var sh = sheets[i];
					var h = sh.href; 
					if (h == cssURL) {
						cnt++;
						if (sh.disabled) {
							// enable a previously disabled stylesheet (ie cannot remove sheets? -> we had to disable them)
							sh.disabled = false;
							return;
						} else {
							if (o_info.debug) o_logwarn("BLoader::loadCSS: style: "+cssURL+" already in document and not disabled! (duplicate add)");
							return;
						}
					}
					// add theme position, theme has to move one down
					if (sh.id == 'o_theme_css') pos = i;
				}
				if (cnt > 1 && o_info.debug) o_logwarn("BLoader::loadCSS: apply styles: num of stylesheets found was not 0 or 1:"+cnt);
				if (loadAfterTheme) {
					// add at the end
					pos = sheets.length;
				}
				// H: stylesheet not yet inserted -> insert				
				doc.createStyleSheet(cssURL, pos);
			} else { // mozilla
				// double check: first try to remove the <link rel="stylesheet"...> tag, using the id.
				var el = jQuery('#' +linkid);
				if (el && el.length > 0) {
					if (o_info.debug) o_logwarn("BLoader::loadCSS: stylesheet already found in doc when trying to add:"+cssURL+", with id "+linkid);
				} else {
					// create the new stylesheet and convince the browser to load the url using @import with protocol 'data'
					//var styles = '@import url("'+cssURL+'");';
					//var newSt = new Element('link', {rel : 'stylesheet', id : linkid, href : 'data:text/css,'+escape(styles) });
					var newSt = jQuery('<link id="' + linkid + '" rel="stylesheet" href="' + cssURL+ '">');
					if (loadAfterTheme) {
						newSt.insertBefore(jQuery('#o_fontSize_css'));
					} else {
						newSt.insertBefore(jQuery('#o_theme_css'));
					}
				}
			}
		} catch(e){
			if(window.console)  console.log(e);
			if (o_info.debug) { // add webbrowser console log
				o_logerr('BLoader::loadCSS: Error when loading CSS from URL::' + cssURL);
			}
		}				
	},

	// Unload a CSS file from the given URL. The linkid represents the DOM id that is used to identify this CSS file
	unLoadCSS : function (cssURL, linkid) {
		var doc = window.document;
		try {
			if(doc.createStyleSheet) { // IE
				var sheets = doc.styleSheets;
				var cnt = 0;
				// calculate relative style url because IE does keep only a 
				// relative URL when the stylesheet is loaded from a relative URL
				var relCssURL = cssURL;
				// calculate base url: protocol, domain and port https://your.domain:8080
				var baseURL = window.location.href.substring(0, window.location.href.indexOf("/", 8)); 
				if (cssURL.indexOf(baseURL) == 0) {
					//remove the base url form the style url
					relCssURL = cssURL.substring(baseURL.length);
				}
				for (i = 0; i < sheets.length; i++) {
					var h = sheets[i].href;
					if (h == cssURL || h == relCssURL) {
						cnt++;
						if (!sheets[i].disabled) {
							sheets[i].disabled = true; // = null;
						} else {
							if (o_info.debug) o_logwarn("stylesheet: when removing: matching url, but already disabled! url:"+h);
						}
					}
				}
				if (cnt != 1 && o_info.debug) o_logwarn("stylesheet: when removeing: num of stylesheets found was not 1:"+cnt);
				
			} else { // mozilla
				var el = jQuery('#' +linkid);
				if (el) {
					el.href = ""; // fix unload problem in safari
					el.remove();
					el = null;
					return;
				} else {
					if (o_info.debug) o_logwarn("no link with id found to remove, id:"+linkid+", url "+cssURL);
				}
			}
		} catch(e){
			if (o_info.debug) { // add webbrowser console log
				o_logerr('BLoader::unLoadCSS: Error when unloading CSS from URL::' + cssURL);
			}
		}				
	}
};

/**
 * The BFormatter object can be used to :
 * - formatt latex formulas using jsMath
 *
 * 18.06.2009 gnaegi@frentix.com 
 */
var BFormatter = {
	// process element with given dom id using jsmath
	formatLatexFormulas : function(domId) {
		try {
			if(typeof MathJax === "undefined") {
				o_mathjax();//will render the whole page
			} else if (MathJax && MathJax.isReady) {
				jQuery(function() {
					MathJax.Hub.Queue(function() {
						if(jQuery('#' + domId + ' .MathJax').length == 0) {
							MathJax.Hub.Typeset(domId)
						}
					});
				})
			} else { // not yet loaded (autoload), load first
				setTimeout(function() {
					BFormatter.formatLatexFormulas(domId);
				}, 100);
			}
		} catch(e) {
			if (window.console) console.log("error in BFormatter.formatLatexFormulas: ", e);
		}
	},
	// Align columns of different tables with the same column count
	// Note: it is best to set the width of the fixed sized colums via css 
	// (e.g. to 1% to make them as small as possible). Table must set to max-size:100% 
	// to not overflow. New width of table can be larger than before because the largest
	// width of each column is applied to all tables. With max-size the browsers magically
	// fix this overflow problem.
	alignTableColumns : function(tableArray) {
		try {
			var cellWidths = new Array();
			// find all widest cells
			jQuery(tableArray).each(function() {
				for(j = 0; j < jQuery(this)[0].rows[0].cells.length; j++){
					var cell = jQuery(this)[0].rows[0].cells[j];
					if(!cellWidths[j] || cellWidths[j] < cell.clientWidth) {
						cellWidths[j] = cell.clientWidth;
					}
				}
			});
			// set same width to columns of all tables
			jQuery(tableArray).each(function() {
				for(j = 0; j < jQuery(this)[0].rows[0].cells.length; j++){
					jQuery(this)[0].rows[0].cells[j].style.width = cellWidths[j]+'px';
				}
			});
		} catch(e) {
			if (window.console) console.log("error in BFormatter.alignTableColumns: ", e);
		}	
	}
};

function o_init() {
	try {
		// all init-on-new-page calls here
		//return opener window
		o_getMainWin().o_afterserver();
		// initialize the business path and social media
		if(window.location.href && window.location.href != null && window.location.href.indexOf('%3A') < 0) {
			var url = window.location.href;
			if(url != null && !(url.lastIndexOf("http", 0) === 0) && !(url.lastIndexOf("https", 0) === 0)) {
				url = o_info.serverUri + url;
			}
			o_info.businessPath = url;
			if(!(typeof o_shareActiveSocialUrl === "undefined")) {
				o_shareActiveSocialUrl();	
			}
		}
	} catch(e) {
		if (o_info.debug) o_log("error in o_init: "+showerror(e));
	}	
}

function o_initEmPxFactor() {
	// read px value for 1 em from hidden div
	o_info.emPxFactor = jQuery('#o_width_1em').width();
	if (o_info.emPxFactor == 0 || o_info.emPxFactor == 'undefined') {
		o_info.emPxFactor = 12; // default value for all strange settings
	}
}

function o_getMainWin() {
	try {
		if (window.OPOL) {
			// other cases the current window is the main window
			return window;
		} else if (window.opener && window.opener.OPOL) {
			// use the opener when opener window is an OpenOLAT window
			return window.opener;
		} 
	} catch (e) {
		if (o_info.debug) { // add webbrowser console log
			o_logerr('Exception while getting main window. rror::"'+showerror(e));
		}
		if (window.console) { // add ajax logger
			console.log('Exception while getting main window. rror::"'+showerror(e), "functions.js");
			console.log(e);
		}	
	}
	throw "Can not find main OpenOLAT window";
}


function o_beforeserver() {
//mal versuche mit jQuery().ready.. erst dann wieder clicks erlauben...
	o_info.linkbusy = true;
	showAjaxBusy();
	// execute iframe specific onunload code on the iframe
	if (window.suppressOlatOnUnloadOnce) {
		// don't call olatonunload this time, reset variable for next time
		window.suppressOlatOnUnloadOnce = false;
	} else if (window.olatonunload) {
		olatonunload();
	}
}

function o_afterserver() {
	o2c = 0;
	o_info.linkbusy = false;
	removeAjaxBusy();
}

function o2cl() {
	try {
		if (o_info.linkbusy) {
			return false;
		} else {
			var doreq = (o2c==0 || confirm(o_info.dirty_form));
			if (doreq) o_beforeserver();
			return doreq;
		}
	} catch(e) {
		if(window.console) console.log(e);
		return false;
	}
}

// the method doesn't set the busy flag
function o2cl_dirtyCheckOnly() {
	try {
		if (o_info.linkbusy) {
			return false;
		} else {
			return (o2c==0 || confirm(o_info.dirty_form));
		}
	} catch(e) {
		if(window.console) console.log(e);
		return false;
	}
}

//for flexi tree
function o2cl_noDirtyCheck() {
	if (o_info.linkbusy) {
		return false;
	} else {
		o_beforeserver();
		return true;
	}
}

function o3cl(formId) {
	if (o_info.linkbusy) {
		return false;
	} else {
		//detect if another flexi form on the screen is dirty too
		var isRegistered = o3c1.indexOf(formId) > -1;
		var flexiformdirty = (isRegistered && o3c1.length > 1) || o3c1.length > 0;
		//check if no other flexi form is dirty
		//otherwise ask if changes should be discarded.
		var doreq = ( !flexiformdirty || confirm(o_info.dirty_form));
		if (doreq) o_beforeserver();
		return doreq;
	}
}

// on ajax poll complete
function o_onc(response) {
	var te = response.responseText;
	BLoader.executeGlobalJS("o_info.last_o_onc="+te+";", 'o_onc');
	//asynchronous! from polling
	o_ainvoke(o_info.last_o_onc,false);
}

function o_allowNextClick() {
	o_info.linkbusy = false;
	removeAjaxBusy();
}

//remove busy after clicking a download link in non-ajax mode
//use LinkFactory.markDownloadLink(Link) to make a link call this method.
function removeBusyAfterDownload(e,target,options){
	o2c = 0;
	o_afterserver();
}

Array.prototype.search = function(s,q){
  var len = this.length;
  for(var i=0; i<len; i++){
    if(this[i].constructor == Array){
      if(this[i].search(s,q)){
        return true;
        break;
      }
     } else {
       if(q){
         if(this[i].indexOf(s) != -1){
           return true;
           break;
         }
      } else {
        if(this[i]==s){
          return true;
          break;
        }
      }
    }
  }
  return false;
}

if(!Function.prototype.curry) {
	Function.prototype.curry = function() {
	    if (arguments.length<1) {
	        return this; //nothing to curry with - return function
	    }
	    var __method = this;
	    var args = Array.prototype.slice.call(arguments);
	    return function() {
	        return __method.apply(this, args.concat(Array.prototype.slice.call(arguments)));
	    }
	}
}

if(!Array.prototype.indexOf) {
	Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
		"use strict";
		if (this == null) {
			throw new TypeError();
        }
        var t = Object(this);
        var len = t.length >>> 0;
        if (len === 0) {
            return -1;
        }
        var n = 0;
        if (arguments.length > 1) {
            n = Number(arguments[1]);
            if (n != n) { // shortcut for verifying if it's NaN
                n = 0;
            } else if (n != 0 && n != Infinity && n != -Infinity) {
                n = (n > 0 || -1) * Math.floor(Math.abs(n));
            }
        }
        if (n >= len) {
            return -1;
        }
        var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
        for (; k < len; k++) {
            if (k in t && t[k] === searchElement) {
                return k;
            }
        }
        return -1;
	}
}


// b_AddOnDomReplacementFinishedCallback is used to add callback methods that are executed after
// the DOM replacement has occured. Note that when not in AJAX mode, those methods will not be 
// executed. Use this callback to execute some JS code to cleanup eventhandlers or alike
//DEPRECATED: listen to event "oo.dom.replacement.after"
var b_onDomReplacementFinished_callbacks=new Array();//array holding js callback methods that should be executed after the next ajax call
function b_AddOnDomReplacementFinishedCallback(funct) {
	b_onDomReplacementFinished_callbacks.push(funct);
}

var b_changedDomEl=new Array();

//same as above, but with a filter to prevent adding a funct. more than once
//funct then has to be an array("identifier", funct) 
// DEPRECATED: listen to event "oo.dom.replacement.after"
function b_AddOnDomReplacementFinishedUniqueCallback(funct) {
	if (funct.constructor == Array){
		//check if it has been added before
		if (b_onDomReplacementFinished_callbacks.search(funct[0])){
			return;
		} 
	}
	b_AddOnDomReplacementFinishedCallback(funct);
}

// main interpreter for ajax mode
var o_debug_trid = 0;
function o_ainvoke(r) {

	// commands
	if(r == undefined) {
		return;
	}
	
	o_info.inainvoke = true;
	var cmdcnt = r["cmdcnt"];
	if (cmdcnt > 0) {
		// let everybody know dom replacement has started
		jQuery(document).trigger("oo.dom.replacement.before");

		b_changedDomEl = new Array();
		
		if (o_info.debug) { o_debug_trid++; }
		var cs = r["cmds"];
		for (var i=0; i<cmdcnt; i++) {
			var acmd = cs[i];
			var co = acmd["cmd"];
			var cda = acmd["cda"];
			var wid = acmd["w"];
			var wi = this.window; // for cross browser window: o_info.wins[wid]; 
			var out;
			if (wi) {
				switch (co) {
					case 1: // Excecute JavaScript Code
						var jsexec = cda["e"];
						BLoader.executeGlobalJS(jsexec, 'o_ainvoker::jsexec');
						if (o_info.debug) o_log("c1: execute jscode: "+jsexec);
					case 2:  // redraw components command
						var cnt = cda["cc"];
						var ca = cda["cps"];
						for (var j=0;  j<cnt; j++) {
							var c1 = ca[j];
							var ciid = c1["cid"]; // component id
							var civis = c1["cidvis"];// component visibility
							var withWrapper = c1["cw"]; // component has a wrapper element, replace only inner content
							var hfrag = c1["hfrag"]; // html fragment of component
							var jsol = c1["jsol"]; // javascript on load
							var hdr = c1["hdr"]; // header
							if (o_info.debug) o_log("c2: redraw: "+c1["cname"]+ " ("+ciid+") "+c1["hfragsize"]+" bytes, listener(s): "+c1["clisteners"]);
							//var con = jQuery(hfrag).find('script').remove(); //Strip scripts
							var hdrco = hdr+"\n\n"+hfrag;
							
							var replaceElement = false;
							var newcId = "o_c"+ciid;
							var newc = jQuery('#' + newcId);
							if (newc == null || newc.length == 0) {
								//not a container, perhaps an element
								newcId = "o_fi"+ciid;
								newc = jQuery('#' + newcId);
								replaceElement = true;
							}
							if (newc != null) {
								var eds = jQuery('div.o_richtext_mce textarea', newc);
								for(var t=0; t<eds.length; t++) {
									try {
										var edId = jQuery(eds.get(t)).attr('id');
										if(typeof top.tinymce != undefined) {
											top.tinymce.remove('#' + edId);
										}
									} catch(e) {
										if(window.console) console.log(e);
									}
								}
								
								var bTooltips = jQuery('body>div.tooltip.in');
								for(var u=0; u<bTooltips.length; u++) {
									try {
										jQuery(bTooltips.get(u)).remove();
									} catch(e) {
										if(window.console) console.log(e);
									}
								}
								
								var jTooltips = jQuery('body>div.ui-tooltip');
								for(var v=0; v<jTooltips.length; v++) {
									try {
										jQuery(jTooltips.get(v)).remove();
									} catch(e) {
										if(window.console) console.log(e);
									}
								}
								
								if(civis) { // needed only for ie 6/7 bug where an empty div requires space on screen
									newc.css('display','');//.style.display="";//reset?
								} else {
									newc.css('display','none'); //newc.style.display="none";
								}
								
								if(replaceElement || !withWrapper) {
									// replace entire DOM element 
									newc.replaceWith(hdrco);	
								} else {
									try{
										newc.empty().html(hdrco);
										//check if the operation is a success especially for IE8
										if(hdrco.length > 0 && newc.get(0).innerHTML == "") {
											newc.get(0).innerHTML = hdrco;
										}
									} catch(e) {
										if(window.console) console.log(e);
										if(window.console) console.log('Fragment',hdrco);
									}
									b_changedDomEl.push(newcId);
								}
								newc = null;

								checkDrakes();
								
								if (jsol != "") {
									BLoader.executeGlobalJS(jsol, 'o_ainvoker::jsol');
								}
							}
						}
						break;
					case 3:  // createParentRedirectTo leads to a full page reload
						wi.o2c = 0;//??
						var rurl = cda["rurl"];
						wi.o_afterserver();
						wi.document.location.replace(rurl);
						break;
					case 5: // create redirect for external resource mapper
						wi.o2c = 0;//??
						var rurl = cda["rurl"];
						//in case of a mapper served media resource (xls,pdf etc.)
						wi.o_afterserver();
						wi.document.location.replace(rurl);//opens non-inline media resource
						break;
					case 6: // createPrepareClientCommand
						wi.o2c = 0;
						wi.o_afterserver();
						break;
					case 7: // JSCSS: handle dynamic insertion of js libs and dynamic insertion/deletion of css stylesheets
						// css remove, add, js add order should makes no big difference? except js calling/modifying css? 
						var loc = wi.document.location;
						var furlp = loc.protocol+"//"+loc.hostname; // e.g. http://my.server.com:8000
						if (loc.port != "" ) furlp += ":"+ loc.port; 
						// 1. unload css file
						var cssrm = cda["cssrm"];
						for (j = 0; j<cssrm.length; j++) {
							var ce = cssrm[j];
							var id = ce["id"];
							var url = furlp + ce["url"];
							BLoader.unLoadCSS(url, id);
							if (o_info.debug) o_log("c7: rm css: id:"+id+" ,url:'"+url+"'");
						}
						// 2) load css file
						var cssadd = cda["cssadd"];
						for (k = 0; k<cssadd.length; k++) {
							var ce = cssadd[k];
							var id = ce["id"];
							var url = furlp + ce["url"];
							var pt = ce["pt"];
							BLoader.loadCSS(url,id,pt);
							if (o_info.debug) o_log("c7: add css: id:"+id+" ,url:'"+url+"'");
						}
						
						// 3) js lib adds
						var jsadd = cda["jsadd"];
						for (l=0; l<jsadd.length; l++) {
							var ce = jsadd[l];
							// 3.1) execute before AJAX-code
							var preJsAdd = ce["before"];
							if (jQuery.type(preJsAdd) === "string") {
								BLoader.executeGlobalJS(preJsAdd, 'o_ainvoker::preJsAdd');
							}
							// 3.2) load js file
							var url = ce["url"];
							var enc = ce["enc"];
							if (jQuery.type(url) === "string") BLoader.loadJS(url, enc, true);
							if (o_info.debug) o_log("c7: add js: "+url);
						}	
						break;	
					default:
						if (o_info.debug) o_log("?: unknown command "+co); 
						break;
				}		
			} else {
				if (o_info.debug) o_log("could not find window??");
			}		
		}

		// BEGIN DEPRECATED DOM REPLACEMENT CALLBACK: new style below
		// execute onDomReplacementFinished callback functions
		var stacklength = b_onDomReplacementFinished_callbacks.length;
		for (mycounter = 0; stacklength > mycounter; mycounter++) {
			
			if (mycounter > 50) {
				break; // emergency break
			}
			var func = b_onDomReplacementFinished_callbacks.shift();
			if (typeof func.length === 'number'){
				if (func[0] == "glosshighlighter") {
					var tmpArr = func[1];
					func = tmpArr;
				 }				
			}
			// don't use execScript here - must be executed outside this function scope so that dom replacement elements are available
			
			//func.delay(0.01);
			func();//TODO jquery
		}
		// END DEPRECATED DOM REPLACEMENT CALLBACK: new style on next line
		
		// let everybody know dom replacement has finished
		jQuery(document).trigger("oo.dom.replacement.after");
	}
	o_info.inainvoke = false;
	
/* minimalistic debugger / profiler	
	BDebugger.logDOMCount();
	BDebugger.logGlobalObjCount();
	BDebugger.logGlobalOLATObjects();
*/
}
/**
 * Method to remove the ajax-busy stuff and let the user click links again. This
 * should only be called from the ajax iframe onload method to make sure the UI
 * does not freeze when the server for whatever reason does not respond as expected.
 */
function clearAfterAjaxIframeCall() {
	if (o_info.linkbusy) {
		// A normal ajax call will clear the linkbusy, so something went wrong in 
		// the ajax channel, e.g. error message from apache or no response from server
		// Call afterserver to remove busy icon clear the linkbusy flag
		o_afterserver();
	}
}

function showAjaxBusy() {
	// release o_info.linkbusy only after a successful server response 
	// - otherwhise the response gets overriden by next request
	setTimeout(function(){
		if (o_info.linkbusy) {
			// try/catch because can fail in full page refresh situation when called before DOM is ready
			try {
				//don't set 2 layers
				if(jQuery('#o_ajax_busy_backdrop').length == 0) {
					jQuery('#o_body').addClass('o_ajax_busy');
					jQuery('#o_ajax_busy').modal({show: true, backdrop: 'static', keyboard: 'false'});
					// fix modal conflic with modal dialogs, make ajax busy appear always above modal dialogs
					jQuery('#o_ajax_busy').after('<div id="o_ajax_busy_backdrop" class="modal-backdrop in"></div>');
					jQuery('#o_ajax_busy>.modal-backdrop').remove();
					jQuery('#o_ajax_busy_backdrop').css({'z-index' : 1200});
				}
			} catch (e) {
				if(window.console) console.log(e);
			}
		}
	}, 700);
}

function removeAjaxBusy() {
	// try/catch because can fail in full page refresh situation when called before page DOM is ready
	try {
		jQuery('#o_body').removeClass('o_ajax_busy');
		jQuery('#o_ajax_busy_backdrop').remove();
		jQuery('#o_ajax_busy').modal('hide');
	} catch (e) {
		if(window.console) console.log(e);
	}
}

function setFormDirty(formId) {
	// sets dirty form content flag to true and renders the submit button
	// of the form with given dom id as dirty.
	// (fg) 
	o2c=1;
	// fetch the form and the forms submit button is identified via the olat 
	// form submit name
	var myForm = document.getElementById(formId);
	if (myForm != null) {
		var mySubmit = myForm.olat_fosm_0;
		if(mySubmit == null){
			mySubmit = myForm.olat_fosm;
		}
		// set dirty css class
		if(mySubmit) mySubmit.className ="btn o_button_dirty";
	}
}


//Pop-up window for context-sensitive help
function contextHelpWindow(URI) {
	helpWindow = window.open(URI, "HelpWindow", "height=760, width=940, left=0, top=0, location=no, menubar=no, resizable=yes, scrollbars=yes, toolbar=no");
	helpWindow.focus();
}

function o_openPopUp(url, windowname, width, height, menubar) {
	// generic window popup function
	attributes = "height=" + height + ", width=" + width + ", resizable=yes, scrollbars=yes, left=100, top=100, ";
	if (menubar) {
		attributes += "location=yes, menubar=yes, status=yes, toolbar=yes";
	} else {
		attributes += "location=no, menubar=no, status=no, toolbar=no";
	}

	var win;
	try {
		win = window.open(url, windowname, attributes);
	} catch(e) {
		win = window.open(url, 'OpenOLAT', attributes);
	}
	
	win.focus();
	if (o_info.linkbusy) {
		o_afterserver();
	}
}

function o_openTab(url) {
	var win = window.open(url, '_blank');
	win.focus();
	if (o_info.linkbusy) {
		o_afterserver();
	}
}

function b_handleFileUploadFormChange(fileInputElement, fakeInputElement, saveButton) {

	fileInputElement.setCustomValidity('');

	if (fileInputElement.hasAttribute('data-max-size')) {
		// check if the file selected does satisfy the max-size constraint
		var maxSize = fileInputElement.getAttribute('data-max-size');
		if (maxSize) {
			var fileSize = formInputFileSize(fileInputElement);
			if (fileSize > maxSize) {
				// show a validation error message, reset the fileInputElement and stop processing
				// to prevent unneeded uploads of potentially really big files
				var trans = jQuery(document).ooTranslator().getTranslator(o_info.locale, 'org.olat.modules.forms.ui');
				var msgLimitExceeded = trans.translate('file.upload.error.limit.exeeded');
				var msgUploadLimit = trans.translate('file.upload.limit');
				var maxSizeFormatted;
				if(maxSize < 250 * 1024) {
					maxSizeFormatted = (maxSize / 1024).toFixed(1) + " KB";
				} else if(maxSize < 250 * 1024 * 1024) {
					maxSizeFormatted = (maxSize / 1024 / 1024).toFixed(1) + " MB";
				} else {
					maxSizeFormatted = (maxSize / 1024 / 1024 / 1024).toFixed(1) + " GB";
				}
				fileInputElement.setCustomValidity(msgLimitExceeded
						+ " (" + msgUploadLimit + ": " + maxSizeFormatted + ")");
			}
		}
	}

	// file upload forms are rendered transparent and have a fake input field that is rendered.
	// on change events of the real input field this method is triggered to display the file 
	// path in the fake input field. See the code for more info on this
	var fileName = fileInputElement.value;
	// remove unix path
	var slashPos = fileName.lastIndexOf('/');
	if (slashPos != -1) {
		fileName=fileName.substring(slashPos + 1); 
	}
	// remove windows path
	slashPos = fileName.lastIndexOf('\\');	
	if (slashPos != -1) {
		fileName=fileName.substring(slashPos + 1); 
	}
	fakeInputElement.value=fileName;
	// mark save button as dirty
	if (saveButton) {
		saveButton.className='o_button_dirty'
	}
	// set focus to next element if available
	var elements = fileInputElement.form.elements;
	for (i=0; i < elements.length; i++) {
		var elem = elements[i];
		if (elem.name == fakeInputElement.name && i+1 < elements.length) {
			elements[i+1].focus();
		}
	}
}

// Return the file size of the selected file in bytes. Returns -1 when API is not working or
// no file was selected.
function formInputFileSize(fileInputElement) {
	try {
		if (!window.FileReader) {
			// file API is not supported do proceed as if the file satisfies the constraint
			return -1;
		}
		if (!fileInputElement || !fileInputElement.files) {
			// missing input element parameter or element is not a file input
			return -1;
		}
		var file = fileInputElement.files[0];
		if (!file) {
			// no file selected!
			return -1;
		}
		return file.size;
	} catch (e) {
		o_logerr('form input file size check failed: ' + e);
	}
	return -1;
}

// goto node must be in global scope to support content that has been opened in a new window 
// with the clone controller - real implementation is moved to course run scope o_activateCourseNode()
function gotonode(nodeid) {
	try {
		// check if o_activateCourseNode method is available in this window
		if (typeof o_activateCourseNode != 'undefined') {
			o_activateCourseNode(nodeid);
		} else {
			// must be content opened using the clone controller - search in opener window
			if (opener && typeof opener.o_activateCourseNode != 'undefined') {
			  opener.o_activateCourseNode(nodeid);
			}		
		}
	} catch (e) {
		alert('Goto node error:' + e);
	}
}

function o_viewportHeight() {
	// based on prototype library
	var prototypeViewPortHeight = jQuery(document).height()
	if (prototypeViewPortHeight > 0) {
		return prototypeViewPortHeight;
	} else {
		return 600; // fallback
	}
}


/**
 *  calculate the height of the inner content area that can be used for 
 *  displaying content without using scrollbars. The height includes the 
 *  margin, border and padding of the main columns
 *  @dependencies: prototype library, jQuery
 *  @author: Florian Gnaegi
 */
OPOL.getMainColumnsMaxHeight =  function(){
	var col1Height = 0,
	col2Height = 0,
	col3Height = 0,
	mainInnerHeight = 0,
	mainHeight = 0,
	mainDomElement,
	col1DomElement = jQuery('#o_main_left_content'),
	col2DomElement = jQuery('#o_main_right_content'),
	col3DomElement = jQuery('#o_main_center_content');
	
	if (col1DomElement != 'undefined' && col1DomElement != null) {
		col1Height = col1DomElement.outerHeight(true);
	}
	if (col2DomElement != 'undefined' && col2DomElement != null){
		col2Height = col2DomElement.outerHeight(true);
	}
	if (col3DomElement != 'undefined' && col3DomElement != null){
		col3Height = col3DomElement.outerHeight(true);
	}

	mainInnerHeight = (col1Height > col2Height ? col1Height : col2Height);
	mainInnerHeight = (mainInnerHeight > col3Height ? mainInnerHeight : col3Height);
	if (mainInnerHeight > 0) {
		return mainInnerHeight;
	} 
	
	// fallback, try to get height of main container
	mainDomElement = jQuery('#o_main');
	if (mainDomElement != 'undefined' && mainDomElement != null) { 
		mainHeight = mainDomElement.height();
	}
	if (mainDomElement > 0) {
		return mainDomElement;
	} 
	// fallback to viewport height	
	return o_viewportHeight();
};

OPOL.adjustHeight = function() {
	// Adjust the height of col1 and 3 based on the max column height. 
	// This is necessary to implement layouts where the two columns have different
	// backgrounds and to enlarge the menu and content area to always show the whole 
	// content. It is also required by the left menu off-canvas feature.
	try {
		var col1El = jQuery('#o_main_left_content');
		var col1 = col1El.length == 0 ? 0 : col1El.outerHeight(true);
		var col2El = jQuery('#o_main_right_content');
		var col2 = col2El.length == 0 ? 0 : col2El.outerHeight(true);
		var col3El = jQuery('#o_main_center_content');
		var col3 = col3El.length == 0 ? 0 : col3El.outerHeight(true);

		var contentHeight = Math.max(col1, col2, col3);
		// Assign new column height
		if (col1El.length > 0) {
			jQuery('#o_main_left').css({'min-height' : contentHeight + "px"});
		}
		if (col2El.length > 0) {
			jQuery('#o_main_right').css({'min-height' : contentHeight + "px"});
		}
		if (col3El.length > 0) {
			jQuery('#o_main_center').css({'min-height' : contentHeight + "px"});
		}
	} catch (e) {
		if(window.console)	console.log(e);
	}
};

/* Set the container page width to full width of the window or use standard page width */
OPOL.setContainerFullWidth = function(full) {
	if (full) {
		jQuery('body').addClass('o_width_full');				
	} else {
		jQuery('body').removeClass('o_width_full');		
	}
	// Update navbar calculations of sites and tabs
	jQuery.proxy(OPOL.navbar.onPageWidthChangeCallback,OPOL.navbar)();
}

/* Register to resize event and fire an event when the resize is finished */
jQuery(window).resize(function() {
	clearTimeout(o_info.resizeId);
	o_info.resizeId = setTimeout(function() {
		jQuery(document).trigger("oo.window.resize.after");
	}, 500);
});

// execute after each DOM replacement cycle and on initial document load
jQuery(document).on("oo.window.resize.after", OPOL.adjustHeight);
jQuery(document).on("oo.dom.replacement.after", OPOL.adjustHeight);
jQuery().ready(OPOL.adjustHeight);


function o_scrollToElement(elem) {
	try {
		o_info.scrolling = true;
		jQuery('html, body').animate({
			scrollTop : jQuery(elem).offset().top
		}, 333, function(e, el) {
			o_info.scrolling = false;
		});
	} catch (e) {
		//console.log(e);
	}
}

function o_popover(id, contentId, loc) {
	if(typeof(loc)==='undefined') loc = 'bottom';
	
	jQuery('#' + id).popover({
    	placement : loc,
    	html: true,
    	trigger: 'click',
    	container: 'body',
    	content: function() { return jQuery('#' + contentId).clone().html(); }
	}).on('shown.bs.popover', function () {
		var clickListener = function (e) {
			jQuery('#' + id).popover('hide');
			jQuery('body').unbind('click', clickListener);
		};
		setTimeout(function() {
			jQuery('body').on('click', clickListener);
		},5);
	});
}

function o_popoverWithTitle(id, contentId, title, loc) {
	if(typeof(loc)==='undefined') loc = 'bottom';
	
	var popover = jQuery('#' + id).popover({
    	placement : loc,
    	html: true,
    	title: title,
    	trigger: 'click',
    	container: 'body',
    	content: function() { return jQuery('#' + contentId).clone().html(); }
	});
	popover.on('shown.bs.popover', function () {
		var clickListener = function (e) {
			jQuery('#' + id).popover('hide');
			jQuery('body').unbind('click', clickListener);
		};
		setTimeout(function() {
			jQuery('body').on('click', clickListener);
		},5);
	});
	return popover;
}

function o_shareLinkPopup(id, text, loc) {
	if(typeof(loc)==='undefined') loc = 'top';
	var elem = jQuery('#' + id);
	elem.popover({
    	placement : loc,
    	html: true,
    	trigger: 'click',
    	container: 'body',
    	content: text
	}).on('shown.bs.popover', function () {
		var clickListener = function (e) {	
			if (jQuery(e.target).data('toggle') !== 'popover' && jQuery(e.target).parents('.popover.in').length === 0) { 
				jQuery('#' + id).popover('hide');
				jQuery('body').unbind('click', clickListener);
			}
		};
		setTimeout(function() {
			jQuery('body').on('click', clickListener);
		}, 5);
	});
	// make mouse over link text work again
	elem.attr('title',elem.attr('data-original-title'));
}

function o_QRCodePopup(id, text, loc) {	
	if(typeof(loc)==='undefined') loc = 'top';
	var elem = jQuery('#' + id);
	elem.popover({
    	placement : loc,
    	html: true,
    	trigger: 'click',
    	container: 'body',
    	content: '<div id="' + id + '_pop" class="o_qrcode"></div>'
	 }).on('shown.bs.popover', function () {
		 o_info.qr = o_QRCode(id + '_pop', (jQuery.isFunction(text) ? text() : text));
		 var clickListener = function (e) {
			 if (jQuery(e.target).data('toggle') !== 'popover' && jQuery(e.target).parents('.popover.in').length === 0) { 
				 jQuery("#" + id).popover('hide');
				 jQuery('body').unbind('click', clickListener);
			 }
		};
		setTimeout(function() {
			jQuery('body').on('click', clickListener);
		}, 5);
	 }).on('hidden.bs.popover', function () {
		 try {
			 o_info.qr.clear();
			 delete o_info.qr;			 
		 } catch(e) {}
	});
	// make mouse over link text work again
	elem.attr('title',elem.attr('data-original-title'));
}
function o_QRCode(id, text) {
	// dynamically load qr code library
	try {
		 BLoader.loadJS(o_info.o_baseURI + "/js/jquery/qrcodejs/qrcode.min.js", 'utf8', true);
		 return new QRCode(document.getElementById(id), text);
	} catch(e) {
		return null;
	}
}

function b_resizeIframeToMainMaxHeight(iframeId) {
	// adjust the given iframe to use as much height as possible
	// (fg)
	var theIframe = jQuery('#' + iframeId);
	if (theIframe != 'undefined' && theIframe != null) {
		var colsHeight = OPOL.getMainColumnsMaxHeight() - 110;
		var potentialHeight = o_viewportHeight() - 100;// remove some padding etc.
		potentialHeight = potentialHeight - theIframe.offset().top;
		// resize now
		var height = (potentialHeight > colsHeight ? potentialHeight : colsHeight);
		theIframe.height(height);
	}
}
// for gui debug mode
var o_debu_oldcn, o_debu_oldtt;

function o_debu_show(cn, tt) {
	if (o_debu_oldcn){
		o_debu_hide(o_debu_oldcn, o_debu_oldtt);
	}
	jQuery(cn).addClass('o_dev_m');
	jQuery(tt).show();

	o_debu_oldtt = tt;
	o_debu_oldcn = cn;
}

function o_debu_hide(cn, tt) {
	jQuery(tt).hide();
	jQuery(cn).removeClass('o_dev_m');
}

function o_dbg_mark(elid) {
	var el = jQuery('#' + elid);
	if (el) {
		el.css('background-color','#FCFCB8');
		el.css('border','3px solid #00F'); 
	}
}

function o_dbg_unmark(elid) {
	var el = jQuery('#' + elid);
	if (el) {
		el.css('border',''); 
		el.css('background-color','');
	}
}

function o_clearConsole() {
 o_log_all="";
 o_log(null);
}

var o_log_all = "";
function o_log(str) {
	if (str) {	
		o_log_all = "\n"+o_debug_trid+"> "+str + o_log_all;
		o_log_all = o_log_all.substr(0,4000);
	}
	var logc = jQuery("#o_debug_cons");
	if (logc) {
		if (o_log_all.length == 4000) o_log_all = o_log_all +"\n... (stripped: to long)... ";
		logc.value = o_log_all;
	}
	if(!jQuery.type(window.console) === "undefined"){
		//firebug log window
		window.console.log(str);
	}
}

function o_logerr(str) {
	o_log("ERROR:"+str);
}

function o_logwarn(str) {
	o_log("WARN:"+str);
}


function showerror(e) {
	var r = "";
    for (var p in e) r += p + ": " + e[p] + "\n";
    return "error detail:\n"+r;
}




// Each flexible.form item with an javascript 'on...' configured calls this fn.
// It is called at least if a flexible.form is submitted.
// It submits the component id as hidden parameters. This specifies which 
// form item should be dispatched by the flexible.form container. A second
// parameter submitted is the action value triggering the submit.
// A 'submit' is not the same as 'submit and validate'. if the form should validate
// is defined by the triggered component.
function o_ffEvent(formNam, dispIdField, dispId, eventIdField, eventInt){
	//set hidden fields and submit form
	var dispIdEl, defDispId,eventIdEl,defEventId;

	dispIdEl = document.getElementById(dispIdField);
	defDispId = dispIdEl.value;
	dispIdEl.value=dispId;
	eventIdEl = document.getElementById(eventIdField);
	defEventId = eventIdEl.value;
	eventIdEl.value=eventInt;
	// manually execute onsubmit method - calling submit itself does not trigger onsubmit event!
	var form = jQuery('#' + formNam);
	var formValid = true;
	jQuery('#' + formNam + ' input[type=file]')
		.filter(function(index, element) {return !element.checkValidity()})
		.each(function(index, element) {
			var valErrorElementId = element.getAttribute('id') + "_validation_error";
			var valErrorElement = document.getElementById(valErrorElementId);
			if (!valErrorElement) {
				valErrorElement = document.createElement('div');
				valErrorElement.setAttribute('class','o_error');
				valErrorElement.setAttribute('id', valErrorElementId);
				element.parentNode.parentNode.appendChild(valErrorElement);
			}
			valErrorElement.innerHTML = element.validationMessage;
			formValid = false;
		});
	if (formValid) {
		var enctype = form.attr('enctype');
		if(enctype && enctype.indexOf("multipart") == 0) {
			form.submit(); // jQuery send onsubmit events
		} else if (document.forms[formNam].onsubmit()) {
			document.forms[formNam].submit();
		}
	}
	dispIdEl.value = defDispId;
	eventIdEl.value = defEventId;
}

function o_IQEvent(formNam){
	if (document.forms[formNam].onsubmit()) {
		document.forms[formNam].submit();
	}
}

function o_TableMultiActionEvent(formNam, action){
	var mActionIdEl = jQuery('#o_mai_' + formNam);
	mActionIdEl.val(action);
	if (document.forms[formNam].onsubmit()) {
		document.forms[formNam].submit();
	}
	mActionIdEl.val('');
}

function o_XHRSubmit(formNam) {
	if(o_info.linkbusy) {
		return false;
	}

	o_beforeserver();
	var push = true;
	var form = jQuery('#' + formNam);
	var enctype = form.attr('enctype');
	if(enctype && enctype.indexOf("multipart") == 0) {
		var iframeName = "openolat-submit-" + ("" + Math.random()).substr(2);
		var iframe = o_createIFrame(iframeName);
		document.body.appendChild(iframe);
		form.attr('target', iframe.name);
		return true;
	} else {
		var data = form.serializeArray();
		if(arguments.length > 1) {
			var argLength = arguments.length;
			for(var i=1; i<argLength; i=i+2) {
				if(argLength > i+1) {
					var argData = new Object();
					argData["name"] = arguments[i];
					argData["value"] = arguments[i+1];
					data[data.length] = argData;
				}
			}
		}

		var targetUrl = form.attr("action");
		jQuery.ajax(targetUrl,{
			type:'POST',
			data: data,
			cache: false,
			dataType: 'json',
			success: function(data, textStatus, jqXHR) {
				try {
					o_ainvoke(data);
					if(push) {
						var businessPath = data['businessPath'];
						var documentTitle = data['documentTitle'];
						var historyPointId = data['historyPointId'];
						if(businessPath) {
							o_pushState(historyPointId, documentTitle, businessPath);
						}
					}
				} catch(e) {
					if(window.console) console.log(e);
				} finally {
					o_afterserver();
				}
			},
			error: o_onXHRError
		});
		return false;
	}
}

function o_createIFrame(iframeName) {
	var $iframe = jQuery('<iframe name="'+iframeName+'" id="'+iframeName+'" src="about:blank" style="position: absolute; top: -9999px; left: -9999px;"></iframe>');
	return $iframe[0];
}

function o_removeIframe(id) {
	jQuery('#' + id).remove();
}

/**
 * Opens the form-dirty dialog. Use the callback to add code that should be executed in case the user 
 * presses the "ignore button" (Code that executes the original action the user initiated)
 */
function o_showFormDirtyDialog(onIgnoreCallback) {
	// open our form-dirty dialog
	o_scrollToElement('#o_top');
	jQuery("#o_form_dirty_message").modal('show');
	jQuery("#o_form_dirty_message .o_form_dirty_ignore").on("click", function() {
		// Remove dialog and all listeners for dirty button
		jQuery("#o_form_dirty_message").modal('hide');
		jQuery("#o_form_dirty_message .o_form_dirty_ignore").off();
		// Execute the ignore callback with original user action
		onIgnoreCallback();
	});
	return false;
}

function o_ffXHREvent(formNam, dispIdField, dispId, eventIdField, eventInt, dirtyCheck, push, submit) {
	if(dirtyCheck && o2c==1) {
		// Copy function arguments and set the dirtyCheck to false for execution in callback.
		// Note that the argument list is dynamic, there are potentially more arguments than
		// listed in the function (e.g. in QTI2)
		var callbackArguments = Array.prototype.slice.call(arguments);
		callbackArguments[5] = false; 		
		var onIgnoreCallback = function() {
			// fire original event when the "ok, delete anyway" button was pressed
			o_ffXHREvent.apply(window, callbackArguments);
		}
		return o_showFormDirtyDialog(onIgnoreCallback);
	} else {
		if(!o2cl_noDirtyCheck()) return false;
	}	
	// Start event execution, start server to prevend concurrent executions of other events. 
	// o_afterserver() called when AJAX call terminates
	o_beforeserver();
	
	var data = new Object();
	if(submit) {
		var form = jQuery('#' + formNam);
		var formData = form.serializeArray();
		var formLength = formData.length;
		for(var i=0; i<formLength; i++) {
			var nameValue = formData[i];//dispatchuri and dispatchevent will be overriden
			if(nameValue.name != 'dispatchuri' && nameValue.name != 'dispatchevent') {
				data[nameValue.name] = nameValue.value;
			}
		}
	}
	
	data['dispatchuri'] = dispId;
	data['dispatchevent'] = eventInt;
	if(arguments.length > 8) {
		var argLength = arguments.length;
		for(var i=8; i<argLength; i=i+2) {
			if(argLength > i+1) {
				data[arguments[i]] = arguments[i+1];
			}
		}
	}
	
	var targetUrl = jQuery('#' + formNam).attr("action");
	jQuery.ajax(targetUrl,{
		type:'POST',
		data: data,
		cache: false,
		dataType: 'json',
		success: function(data, textStatus, jqXHR) {
			try {
				o_ainvoke(data);
				if(push) {
					var businessPath = data['businessPath'];
					var documentTitle = data['documentTitle'];
					var historyPointId = data['historyPointId'];
					if(businessPath) {
						o_pushState(historyPointId, documentTitle, businessPath);
					}
				}
			} catch(e) {
				if(window.console) console.log(e);
			} finally {
				o_afterserver();
			}
		},
		error: o_onXHRError
	})
}

function o_ffXHRNFEvent(formNam, dispIdField, dispId, eventIdField, eventInt) {
	var data = new Object();
	data['dispatchuri'] = dispId;
	data['dispatchevent'] = eventInt;
	if(arguments.length > 5) {
		var argLength = arguments.length;
		for(var i=5; i<argLength; i=i+2) {
			if(argLength > i+1) {
				data[arguments[i]] = arguments[i+1];
			}
		}
	}
	
	var targetUrl = jQuery('#' + formNam).attr("action");
	jQuery.ajax(targetUrl,{
		type:'POST',
		data: data,
		cache: false,
		dataType: 'json',
		success: function(data, textStatus, jqXHR) {
			//no response
		}
	})
}

function o_XHRWikiEvent(link) {
	var href = jQuery(link).attr('href');
	if(href.indexOf(o_info.serverUri) == 0) {
		href = href.substring(o_info.serverUri.length, href.length);
	}
	o_XHREvent(href, false, true);
	return false;
}

function o_XHREvent(targetUrl, dirtyCheck, push) {
	if(dirtyCheck && o2c==1) {
		// Copy function arguments and set the dirtyCheck to false for execution in callback.
		// Note that the argument list is dynamic, there are potentially more arguments than
		// listed in the function
		var callbackArguments = Array.prototype.slice.call(arguments);
		callbackArguments[1] = false; 		
		var onIgnoreCallback = function() {
			// fire original event when the "ok, delete anyway" button was pressed
			o_XHREvent.apply(window, callbackArguments);
		}
		return o_showFormDirtyDialog(onIgnoreCallback);		
	} else {
		if(!o2cl_noDirtyCheck()) return false;
	}
	// Start event execution, start server to prevend concurrent executions of other events. 
	// o_afterserver() called when AJAX call terminates
	o_beforeserver();
	
	var data = new Object();
	if(arguments.length > 3) {
		var argLength = arguments.length;
		for(var i=3; i<argLength; i=i+2) {
			if(argLength > i+1) {
				data[arguments[i]] = arguments[i+1];
			}
		}
	}
	
	jQuery.ajax(targetUrl,{
		type:'POST',
		data: data,
		cache: false,
		dataType: 'json',
		success: function(data, textStatus, jqXHR) {
			try {
				
				if(push) {
					try {
						var businessPath = data['businessPath'];
						var documentTitle = data['documentTitle'];
						var historyPointId = data['historyPointId'];
						if(businessPath) {
							// catch separately - nothing must fail here!
							o_pushState(historyPointId, documentTitle, businessPath);
						}
					} catch(e) {
						if(window.console) console.log(e);
					}
				}
				o_ainvoke(data);
			} catch(e) {
				if(window.console) console.log(e);
			} finally {
				o_afterserver();
			}
		},
		error: o_onXHRError
	})
	
	return false;
}

//by pass every check and don't wait a response from the response
//typically used to send GUI settings back to the server
function o_XHRNFEvent(targetUrl) {
	var data = new Object();
	if(arguments.length > 1) {
		var argLength = arguments.length;
		for(var i=1; i<argLength; i=i+2) {
			if(argLength > i+1) {
				data[arguments[i]] = arguments[i+1];
			}
		}
	}
	
	jQuery.ajax(targetUrl,{
		type:'POST',
		data: data,
		cache: false,
		dataType: 'json',
		success: function(data, textStatus, jqXHR) {
			//ok
		},
		error: o_onXHRError
	})
}

function o_onXHRError(jqXHR, textStatus, errorThrown) {
	o_afterserver();
	if(401 == jqXHR.status) {
		var msg = o_info.oo_noresponse.replace("reload.html", window.document.location.href);
		showMessageBox('error', o_info.oo_noresponse_title, msg, undefined);
	} else if(window.console) {
		console.log('Error status 2', textStatus, errorThrown, jqXHR.responseText);
		console.log(jqXHR);
	}
}

function o_pushState(historyPointId, title, url) {
	try {
		var data = new Object();
		data['businessPath'] = url;
		data['historyPointId'] = historyPointId;
		
		if(url != null && !(url.lastIndexOf("http", 0) === 0) && !(url.lastIndexOf("https", 0) === 0)) {
			url = o_info.serverUri + url;
		}
		o_info.businessPath = url;
		if(!(typeof o_shareActiveSocialUrl === "undefined")) {
			o_shareActiveSocialUrl();	
		}
		if(window.history && !(typeof window.history === "undefined") && window.history.pushState) {
			window.history.pushState(data, title, url);
		} else {
			window.location.hash = historyPointId;
		}
	} catch(e) {
		if(window.console) console.log(e, url);
	}
}

function o_toggleMark(el) {
	var current = jQuery('i', el).attr('class');
	if(current.indexOf('o_icon_bookmark_add') >= 0) {
		jQuery('i', el).removeClass('o_icon_bookmark_add').addClass('o_icon_bookmark');
	} else {
		jQuery('i', el).removeClass('o_icon_bookmark').addClass('o_icon_bookmark_add');
	}
}

/**
 * Register a dragula object, the object will be associated
 * with a DOM element with the .o_drake class. If the class
 * is absent of the DOM, all drakes will be desstroyed.
 * 
 * @param drake
 * @returns drake
 */
function registerDrake(drake) {
	o_info.drakes.push(drake);
	return drake;
}

function destroyDrakes() {
	if(o_info.drakes !== "undefined" && o_info.drakes != null && o_info.drakes.length > 0) {
		for(var i=o_info.drakes.length; i-->0; ) {
			try {
				o_info.drakes[i].destroy();
			} catch(e) {
				if(window.console) console.log(e);
			}
			o_info.drakes.pop();
		}
	}
}

function checkDrakes() {
	if(o_info.drakes !== "undefined" && o_info.drakes != null && o_info.drakes.length > 0) {
		if(jQuery(".o_drake").length == 0) {
			destroyDrakes();
		}
	}
}

//try to mimic the FileUtils.normalizeFilename method
function o_normalizeFilename(filename) {
	filename = filename.replace(/\s/g, "_")
	var replaceByUnderscore = [ "/", ",", ":", "(", ")" ];
	for(var i=replaceByUnderscore.length; i-->0; ) {
		filename = filename.split(replaceByUnderscore[i]).join("_");
	}

	var beautifyGermanUnicode = [ "\u00C4", "\u00D6", "\u00DC", "\u00E4", "\u00F6", "\u00E6", "\u00FC", "\u00DF", "\u00F8", "\u2205" ],
		beautifyGermanReplacement = [ "Ae", "Oe",     "Ue",     "ae",     "oe",     "ae",     "ue",     "ss",     "o",      "o" ];
	for(var i=beautifyGermanUnicode.length; i-->0; ) {
		filename = filename.split(beautifyGermanUnicode[i]).join(beautifyGermanReplacement[i]);
	}

	try {//if something is not supported by the browser
		filename = filename.normalize('NFKD');
		filename = filename.replace("/\p{InCombiningDiacriticalMarks}+/g","");
		filename = filename.replace("/\W+/g", "");
	} catch(e) {
		if(window.console) console.log(e);
	}
	return filename;
}

//
// param formId a String with flexi form id
function setFlexiFormDirtyByListener(e){
	setFlexiFormDirty(e.data.formId, e.data.hideMessage);
}

function setFlexiFormDirty(formId, hideMessage){
	var isRegistered = o3c.indexOf(formId) > -1;
	if(!isRegistered){
		o3c.push(formId);
	}
	jQuery('#'+formId).each(function() {
		var submitId = jQuery(this).data('FlexiSubmit');
		if(submitId != null) {
			jQuery('#'+submitId).addClass('btn o_button_dirty');
			o2c = (hideMessage ? 0 : 1);
		}
	});
}

//
//
function o_ffRegisterSubmit(formId, submElmId){
	jQuery('#'+formId).data('FlexiSubmit', submElmId);
}

function dismissInfoBox(uuid) {
	javascript:jQuery('#' + uuid).remove();
	return true;
}
/*
* renders an info msg that slides from top into the window
* and hides automatically
*/
function showInfoBox(title, content){
	// Factory method to create message box
	var uuid = Math.floor(Math.random() * 0x10000 /* 65536 */).toString(16);
	var info = '<div id="' + uuid
	     + '" class="o_alert_info"><div class="alert alert-info clearfix o_sel_info_message"><a class="o_alert_close o_sel_info_close" href="javascript:;" onclick="dismissInfoBox(\'' + uuid + '\')"><i class="o_icon o_icon_close"> </i></a><h3><i class="o_icon o_icon_info"> </i> '
		 + title + '</h3><p>' + content + '</p></div></div>';
    var msgCt = jQuery('#o_messages').prepend(info);
    // Hide message automatically based on content length
    var time = (content.length > 150) ? 8000 : ((content.length > 70) ? 6000 : 4000);

    // Callback to remove after reading
    var cleanup = function() {
    	jQuery('#' + uuid)
    		.transition({top : '-100%'}, 333, function() {
    			jQuery('#' + uuid).remove();
    		});    	
    };
    // Show info box now
    o_info.scrolling = true;
    jQuery('#' + uuid).show().transition({ top: 0 }, 333);
    // Visually remove message box immediately when user clicks on it
    jQuery('#' + uuid).click(function(e) {
    	cleanup();
    });
	o_scrollToElement('#o_top');
	
    // Help GC, prevent cyclic reference from on-click closure
    title = null;
    content = null;
    msgCt = null;
    
    setTimeout(function(){
		try {
			cleanup();
		} catch(e) {
			//possible if the user has closed the window
		}
	}, time);
}
/*
* renders an message box which the user has to click away
* The last parameter buttonCallback is optional. if a callback js 
* function is given it will be execute when the user clicks ok or closes the message box
*/
function showMessageBox(type, title, message, buttonCallback) {
	if(type == 'info'){
		showInfoBox(title, message);
		return null;
	} else {
		var content = '<div id="myFunctionalModal" class="modal fade" role="dialog"><div class="modal-dialog"><div class="modal-content">';
		content += '<div class="modal-header"><button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>';
        content += '<h4 class="modal-title">' + title + '</h4></div>';	
		content += '<div class="modal-body alert ';
		if("warn" == type) {
			content += 'alert-warning';
		} else if("error" == type) {
			content += 'alert-danger';
		} else {
			content += 'alert-info';
		}
		content += '"><p>' + message + '</p></div></div></div></div>';
		jQuery('#myFunctionalModal').remove();
		jQuery('body').append(content);
		               
		var msg = jQuery('#myFunctionalModal').modal('show').on('hidden.bs.modal', function (e) {
			jQuery('#myFunctionalModal').remove();
		});
		o_scrollToElement('#o_top');
		return msg;
	}
}

/*
 * For standard tables
 */
function o_table_toggleCheck(ref, checked) {
	var tb_checkboxes = document.forms[ref].elements["tb_ms"];
	len = tb_checkboxes.length;
	if (typeof(len) == 'undefined') {
		tb_checkboxes.checked = checked;
	}
	else {
		var i;
		for (i=0; i < len; i++) {
			tb_checkboxes[i].checked=checked;
		}
	}
}

/*
 * For menu tree
 */
function onTreeStartDrag(event, ui) {
	jQuery(event.target).addClass('o_dnd_proxy');
}

function onTreeStopDrag(event, ui) {
	jQuery(event.target).removeClass('o_dnd_proxy');
}

function onTreeDrop(event, ui) {
	var dragEl = jQuery(ui.draggable[0]);
	var el = jQuery(this);
	el.css({position:'', width:''});
	var url =  el.droppable('option','endUrl');
	if(url.lastIndexOf('/') == (url.length - 1)) {
		url = url.substring(0,url.length-1);
	}
	var dragId = dragEl.attr('id')
	var targetId = dragId.substring(2, dragId.length);
	url += '%3Atnidle%3A' + targetId;

	var droppableId = el.attr('id');
	if(droppableId.indexOf('ds') == 0) {
		url += '%3Asne%3Ayes';
	} else if(droppableId.indexOf('dt') == 0) {
		url += '%3Asne%3Aend';
	}
	jQuery('.ui-droppable').each(function(index, el) {
		jQuery(el).droppable( "disable" );
	});
	o_XHREvent(url + '/', false, false);
}

function treeAcceptDrop(el) {
	return true;
}

function treeAcceptDrop_notWithChildren(el) {
	var accept = false;
	
	var dragEl = jQuery(el);
	var dragElId = dragEl.attr('id');
	if(dragElId != undefined && (dragElId.indexOf('dd') == 0 ||
		dragElId.indexOf('ds') == 0 || dragElId.indexOf('dt') == 0 ||
		dragElId.indexOf('da') == 0 || dragElId.indexOf('row') == 0)) {

		var dropEl = jQuery(this)
		var dropElId = dropEl.attr('id');//dropped
		var dragNodeId = dragElId.substring(2, dragElId.length);
		var dropId = dropElId.substring(2, dropElId.length);
		if(dragNodeId != dropId) {
			var containerEl = jQuery('#dd' + dragNodeId).parents('li');
			if(containerEl.length > 0 && jQuery(containerEl.get(0)).find('#dd' + dropId).length == 0) {
				accept = true;
			}
		}
	}
	
	return accept;
}

function treeAcceptDrop_portfolio(el) {
	var accept = false;
	
	var dragEl = jQuery(el);
	var dragElId = dragEl.attr('id');
	if(treeNode_isDragNode(dragElId)) {
		var dropEl = jQuery(this);
		var dropElId = dropEl.attr('id');//dropped
		var dragNodeId = dragElId.substring(2, dragElId.length);
		var dropId = dropElId.substring(2, dropElId.length);
		var sibling = dragElId.indexOf('ds') == 0 || dragElId.indexOf('dt') == 0;
		if(dragNodeId != dropId) {
			var dragType = treeNode_portfolioType(dragEl);
			var dropType = treeNode_portfolioType(dropEl);
			if(dragType == "artefact") {
				if(dropType == "page" || dropType == "struct" || dropType == "artefact") {
					accept = true;
				}
			} else if(dragType == "struct") {
				if(dropType == "page" || dropType == "struct") {
					accept = true;
				}
			} else if(dragType == "page") {
				if(dropType == "map" || dropType == "page") {
					accept = true;
				}
			}
		}
	}

	return accept;
}

function treeNode_portfolioType(el) {
	var nodeEl = jQuery(el.get(0));
	var type = treeNode_portfolioTypes(nodeEl);
	if(type == null) {
		var parentLink = nodeEl.parent('a');
		if(parentLink.length > 0) {
			type = treeNode_portfolioTypes(jQuery(parentLink.get(0)));
		} else if(nodeEl.attr('id').indexOf('ds') == 0) {
			var prevEl = nodeEl.prev('div');
			if(prevEl.length > 0) {
				type = treeNode_portfolioTypes(prevEl);
			}
		} else if(nodeEl.attr('id').indexOf('dt') == 0) {
			var prevEl = nodeEl.next('div');
			if(prevEl.length > 0) {
				type = treeNode_portfolioTypes(prevEl);
			}
		}
	}
	return type;
}

function treeNode_portfolioTypes(nodeEl) {
	if(nodeEl.find === undefined) {
		return null;
	} else if(nodeEl.find(".o_ep_icon_struct").length > 0 || nodeEl.hasClass('o_ep_icon_struct')) {
		return "struct";
	} else if(nodeEl.find(".o_ep_icon_page").length > 0 || nodeEl.hasClass('o_ep_icon_page')) {
		return "page";
	} else if(nodeEl.find(".o_ep_icon_map").length > 0 || nodeEl.hasClass('o_ep_icon_map')) {
		return "map";
	} else if(nodeEl.find(".o_ep_artefact").length > 0 || nodeEl.hasClass('o_ep_artefact')) {
		return "artefact";
	}
	return null;
}

function treeNode_isDragNode(elId) {
	if(elId != undefined && (elId.indexOf('dd') == 0 ||
			elId.indexOf('ds') == 0 || elId.indexOf('dt') == 0 ||
			elId.indexOf('da') == 0 || elId.indexOf('row') == 0)) {
		return true;
	}
	return false;
}

/*
 * For checkbox
 */
function o_choice_toggleCheck(ref, checked) {
	var checkboxes = document.forms[ref].elements;
	len = checkboxes.length;
	if (typeof(len) == 'undefined') {
		checkboxes.checked = checked;
	}
	else {
		var i;
		for (i=0; i < len; i++) {
			if (checkboxes[i].type == 'checkbox' && checkboxes[i].getAttribute('class') == 'o_checkbox' && checkboxes[i].getAttribute('disabled') != 'disabled') {
				checkboxes[i].checked=checked;
			}
		}
	}
}

/*
 * For briefcase
 */
function b_briefcase_isChecked(ref, warning_text) {
	var i;
	var myElement = document.getElementById(ref);
	var numselected = 0;
	for (i=0; myElement.elements[i]; i++) {
		if (myElement.elements[i].type == 'checkbox' && myElement.elements[i].name == 'paths' && myElement.elements[i].checked) {
			numselected++;
		}
	}
	
	if (numselected < 1) {
		alert(warning_text);
		return false;
	}
	return true;
}
function b_briefcase_toggleCheck(ref, checked) {
	var myElement = document.getElementById(ref);
	len = myElement.elements.length;
	var i;
	for (i=0; i < len; i++) {
		if (myElement.elements[i].name=='paths') {
			myElement.elements[i].checked=checked;
		}
	}
}


/*
 * print command, prints iframes when available
 */
function o_doPrint() {
	// When we have an iframe, issue print command on iframe directly
	var iframes =  jQuery('div.o_iframedisplay iframe');
	if (iframes.length > 0) {
		try {
			var iframe = iframes[0];
			frames[iframe.name].focus();
			frames[iframe.name].print();
			return;
		} catch (e) {
			// When iframe content renames the window, the method above does not work.
			// We use best guess code to find the target iframe in the window frames list
			for (i=0; frames.length > i; i++) {
				iframe = frames[i];
				if (iframe.name == 'oaa0') continue; // skip ajax iframe
				var domFrame = document.getElementsByName(iframe.name)[0];
				if (domFrame && domFrame.getAttribute('class') == 'ext-shim') continue; // skip ext shim iframe
				// Buest guess is that this is our renamed target iframe			
				if (iframe.name != '') {
					try {
						frames[iframe.name].focus();
						frames[iframe.name].print();				
					} catch (e) {
						// fallback to window print
						window.print()
					}
					return;
				}
			}		
			// fallback to window print
			window.print()
		}
	} else {
		// no iframes found, print window
		window.print()
	}
}


/*
 * Attach event listeners to enable inline translation tool hover links
 */ 
function b_attach_i18n_inline_editing() {
	// Add hover handler to display inline edit links
	jQuery('span.o_translation_i18nitem').hover(function() {
		jQuery(this.firstChild).show();
	},function(){
		jQuery('a.o_translation_i18nitem_launcher').hide();
	});
	// Add highlight effect on link to show which element is affected by this link
	jQuery('a.o_translation_i18nitem_launcher').hover(function() {	
		var parent = jQuery(this).parent('span.o_translation_i18nitem')
		parent.effect("highlight");
	});
	// Add to on ajax ready callback for next execution
	b_AddOnDomReplacementFinishedCallback(b_attach_i18n_inline_editing);
}

function b_hideExtMessageBox() {
	//for compatibility
}
 
 
/**
 * Minimalistic debugger to find ever growing list of DOM elements, 
 * global variables or OLAT managed variables. To use it, uncomment
 * lines in o_ainvoke()
 */
var BDebugger = {
	_lastDOMCount : 0,
	_lastObjCount : 0,
	_knownGlobalOLATObjects : ["o_afterserver","o_onc","o_getMainWin","o_ainvoke","o_info","o_beforeserver","o_ffEvent","o_openPopUp","o_debu_show","o_logwarn","o_dbg_unmark","o_ffRegisterSubmit","o_clearConsole","o_init","o_log","o_allowNextClick","o_dbg_mark","o_debu_hide","o_logerr","o_debu_oldcn","o_debu_oldtt","o_debug_trid","o_log_all"],
		
	_countDOMElements : function() {
		return document.getElementsByTagName('*').length;
	},
	_countGlobalObjects : function() {
			var objCount=0; 
			for (prop in window) {
				objCount++;
			} 
			return objCount;
	},
	
	logDOMCount : function() {
		var self = BDebugger;
		var DOMCount=self._countDOMElements();
		var diff = DOMCount - self._lastDOMCount;
		console.log( (diff > 0 ? "+" : "") + diff + " \t" + DOMCount + " \tDOM element count after DOM replacement");
		self._lastDOMCount = DOMCount;
		DOMCount = null;
	},

	logGlobalObjCount : function() {	
		var self = BDebugger;
		var objCount = self._countGlobalObjects();
		var diff = objCount - self._lastObjCount;
		console.log( (diff > 0 ? "+" : "") + diff + " \t" + objCount + " \tGlobal object count after DOM replacement");
		self._lastObjCount = objCount;
		objCount = null;
	},
	
	logGlobalOLATObjects : function() {
		var self = BDebugger;
		var OLATObjects = new Array();
		for (prop in window) {
			if (prop.indexOf("o_") == 0 && self._knownGlobalOLATObjects.indexOf(prop) == -1) {
				OLATObjects.push(prop);
			}
		} 	
		if (OLATObjects.length > 0) {
			console.log(OLATObjects.length + " global OLAT objects found:");
			OLATObjects.each(function(o){
				console.log("\t" + typeof window[o] + " \t" + o);
			});
		}
	}
}

var OOEdusharing = {
		
	start: function() {
		if (o_info.edusharing_enabled) {
			OOEdusharing.render();
			jQuery(document).on("oo.dom.replacement.after", OOEdusharing.render);
			OOEdusharing.enableMetadataToggler();
		}
	},
		
	replaceWithSpinner: function(node, width, height) {
		var spinnerHtml = "<div style='";
		if (width > 0) {
			spinnerHtml += "width:" + width + "px;";
		}
		if (height > 0) {
			spinnerHtml += "height:" + height + "px;";
		}
		spinnerHtml += "'>";
		spinnerHtml += "<div class='edusharing_spinner_inner'><div class='edusharing_spinner1'></div></div>";
		spinnerHtml += "<div class='edusharing_spinner_inner'><div class='edusharing_spinner2'></div></div>";
		spinnerHtml += "<div class='edusharing_spinner_inner'><div class='edusharing_spinner3'></div></div>";
		spinnerHtml += "</div>";
		
		var spinner = jQuery(spinnerHtml);
		node.before(spinner);
		node.remove();
		return spinner;
	},
	
	replaceGoTo: function(html, identifier) {
		var url = o_info.uriprefix.replace("auth", "edusharing") + "goto?identifier=" + identifier;
		html = html.replace("{{{LMS_INLINE_HELPER_SCRIPT}}}", url)
		return html;
	},
	
	replaceWithRendered: function(node, identifier, width, height, esClass, showLicense, showInfos, isIFrame) {
		var url = o_info.uriprefix.replace("auth", "edusharing") + "render?identifier=" + identifier;
		if (width > 0) {
			url = url + "&width=" + width;
		}
		if (height) {
			url = url + "&height=" + height;
		}
		
		var containerHtml = "<div class='o_edusharing_container";
		if (typeof esClass != 'undefined') {
			containerHtml += " " + esClass;
		}
		if (isIFrame) {
			containerHtml += " o_in_iframe";
		}
		if ('hide' === showLicense) {
			containerHtml += " o_hide_license";
		}
		if ('hide' === showInfos) {
			containerHtml += " o_hide_infos";
		}
		containerHtml += "'>";
		containerHtml += "</div>";
		
		var container = jQuery(containerHtml);
		
		jQuery.ajax({
			type: "GET",
			url: url,
			dataType : 'html',
			success : function(data){
				var goToData = OOEdusharing.replaceGoTo(data, identifier);
				var esNode = container.append(goToData);
				node.replaceWith(esNode);
			},
			error : function(XMLHttpRequest, textStatus, errorThrown) {
				node.replaceWith("<div class='o_warning'>edu-sharing not available</div>");
			}
		})
	},
		
	replace: function(node, isIFrame) {
		var identifier = node.data("es_identifier");
		var width = node.attr("width");
		var height = node.attr("height");
		var esClass = node.attr('class');
		var showLicense = node.data("es_show_license");
		var showInfos = node.data("es_show_infos");
		
		var spinner = OOEdusharing.replaceWithSpinner(node, width, height);
		OOEdusharing.replaceWithRendered(spinner, identifier, width, height, esClass, showLicense, showInfos, isIFrame);
	},
	
	/**
	 * Replace the edu-sharing nodes with the real resources from the edu-sharing rendering service.
	 */
	render: function() {
		var esNodes = jQuery("[data-es_identifier]");
		if (esNodes.length > 0) {
			esNodes.each(function() {
				var node = jQuery( this );
				OOEdusharing.replace(node, false);
			});
		}
		// Handle inside internal iFrames as well
		var iFrames = jQuery(".o_iframe_rel");
		if (iFrames.length > 0) {
			iFrames.each(function() {
				var iFrame = jQuery( this );
				iFrame.on('load', function(){
					iFrame.contents().on('click', OOEdusharing.toggleMetadata);
					var iFrameEsNodes = iFrame.contents().find("[data-es_identifier]");
					if (iFrameEsNodes.length > 0) {
						iFrameEsNodes.each(function() {
							var iFrameEsNode = jQuery( this );
							OOEdusharing.replace(iFrameEsNode, true);
						});
					}
				});
			});
		}
	},
	
	/**
	 * Toggle edu-sharing metadata.
	 * see https://github.com/edu-sharing/plugin-moodle/blob/master/filter/edusharing/amd/src/edu.js
	 */
	toggleMetadata: function (e) {
		if (jQuery(e.target).closest(".edusharing_metadata").length) {
			//clicked inside ".edusharing_metadata" - do nothing
		} else if (jQuery(e.target).closest(".edusharing_metadata_toggle_button").length) {
			jQuery(".edusharing_metadata").hide();
			toggle_button = jQuery(e.target);
			metadata = toggle_button.parent().find(".edusharing_metadata");
			if (metadata.hasClass('open')) {
				metadata.toggleClass('open');
				metadata.hide();
			} else {
				jQuery(".edusharing_metadata").removeClass('open');
				metadata.toggleClass('open');
				metadata.show();
			}
		} else {
			jQuery(".edusharing_metadata").hide();
			jQuery(".edusharing_metadata").removeClass('open');
		}
	},
	enableMetadataToggler: function() {
		jQuery(document).click(OOEdusharing.toggleMetadata);
	}
}

jQuery( document ).ready(function() {
	OOEdusharing.start();
});