Skip to content
Snippets Groups Projects
functions.js 62.3 KiB
Newer Older
Alan Moran's avatar
Alan Moran committed
/**
 * This file contains helper methods for the olatcore web app framework and the
 * learning management system OLAT
 */
Alan Moran's avatar
Alan Moran committed
//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;
Alan Moran's avatar
Alan Moran committed
//debug flag for this file, to enable debugging to the olat.log set JavaScriptTracingController to level debug
Alan Moran's avatar
Alan Moran committed

/**
 * 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) {
Alan Moran's avatar
Alan Moran committed
				notLoaded = false;
			};
		});
		// second check for script loaded via ajax call
		if (jQuery.inArray(jsURL, this._ajaxLoadedJS) != -1) notLoaded = false;
Alan Moran's avatar
Alan Moran committed
		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',
					success: function(script, textStatus, jqXHR) {
						//BLoader.executeGlobalJS(script, 'loadJS');
					}
Alan Moran's avatar
Alan Moran committed
				});
				this._ajaxLoadedJS.push(jsURL);
			} else {
Alan Moran's avatar
Alan Moran committed
			}
			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);
Alan Moran's avatar
Alan Moran committed
			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));
			}
			if(jQuery(document).ooLog().isDebugEnabled()) jQuery(document).ooLog('debug','BLoader::executeGlobalJS: Error when executing JS code in contextDesc::' + contextDesc + ' error::"'+showerror(e)+' for: '+escape(jsString), "functions.js::BLoader::executeGlobalJS::" + contextDesc);
Alan Moran's avatar
Alan Moran committed
			// 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;
Alan Moran's avatar
Alan Moran committed
				}
				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				
				var mystyle = 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.size() > 0) {
Alan Moran's avatar
Alan Moran committed
					if (o_info.debug) o_logwarn("BLoader::loadCSS: stylesheet already found in doc when trying to add:"+cssURL+", with id "+linkid);
					return;
				} 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" type="text/css" href="' + cssURL+ '">');
					if (loadAfterTheme) {
						newSt.insertBefore(jQuery('#o_fontSize_css'));
						newSt.insertBefore(jQuery('#o_theme_css'));
Alan Moran's avatar
Alan Moran committed
				}
			}
		} catch(e){
			if(window.console)  console.log(e);
Alan Moran's avatar
Alan Moran committed
			if (o_info.debug) { // add webbrowser console log
				o_logerr('BLoader::loadCSS: Error when loading CSS from URL::' + cssURL);
			}
			if(jQuery(document).ooLog().isDebugEnabled()) jQuery(document).ooLog('debug','BLoader::loadCSS: Error when loading CSS from URL::' + cssURL, "functions.js::BLoader::loadCSS");
Alan Moran's avatar
Alan Moran committed
		}				
	},

	// 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);
Alan Moran's avatar
Alan Moran committed
				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);
			}
			if(jQuery(document).ooLog().isDebugEnabled()) jQuery(document).ooLog('debug','BLoader::unLoadCSS: Error when unloading CSS from URL::' + cssURL, "functions.js::BLoader::loadCSS");
Alan Moran's avatar
Alan Moran committed
		}				
	}
};

/**
 * 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);
Alan Moran's avatar
Alan Moran committed
			}
		} catch(e) {
			if (window.console) console.log("error in BFormatter.formatLatexFormulas: ", e);
Alan Moran's avatar
Alan Moran committed
		}
	},
	// 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);
		}	
Alan Moran's avatar
Alan Moran committed
	}
};

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();	
			}
		}
Alan Moran's avatar
Alan Moran committed
	} catch(e) {
		if (o_info.debug) o_log("error in o_init: "+showerror(e));
	}	
}

hg's avatar
hg committed
function o_initEmPxFactor() {
Alan Moran's avatar
Alan Moran committed
	// read px value for 1 em from hidden div
hg's avatar
hg committed
	o_info.emPxFactor = jQuery('#o_width_1em').width();
Alan Moran's avatar
Alan Moran committed
	if (o_info.emPxFactor == 0 || o_info.emPxFactor == 'undefined') {
		o_info.emPxFactor = 12; // default value for all strange settings
		if(jQuery(document).ooLog().isDebugEnabled()) jQuery(document).ooLog('debug','Could not read with of element b_width_1em, set o_info.emPxFactor to 12', "functions.js");
Alan Moran's avatar
Alan Moran committed
	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);
Alan Moran's avatar
Alan Moran committed
function o_beforeserver() {
//mal versuche mit jQuery().ready.. erst dann wieder clicks erlauben...
Alan Moran's avatar
Alan Moran committed
	o_info.linkbusy = true;
Alan Moran's avatar
Alan Moran committed
	// 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);
Alan Moran's avatar
Alan Moran committed
		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 {
Alan Moran's avatar
Alan Moran committed

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;
	}
}


Alan Moran's avatar
Alan Moran committed
// 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
Alan Moran's avatar
Alan Moran committed
var b_onDomReplacementFinished_callbacks=new Array();//array holding js callback methods that should be executed after the next ajax call
function b_AddOnDomReplacementFinishedCallback(funct) {
	var debug = jQuery(document).ooLog().isDebugEnabled();
	
	if(debug) jQuery(document).ooLog('debug',"callback stack size: " + b_onDomReplacementFinished_callbacks.length, "functions.js ADD"); 
	if (debug && b_onDomReplacementFinished_callbacks.toSource) {
		jQuery(document).ooLog('debug',"stack content"+b_onDomReplacementFinished_callbacks.toSource(), "functions.js ADD")
	};
Alan Moran's avatar
Alan Moran committed

	b_onDomReplacementFinished_callbacks.push(funct);
	if(debug) jQuery(document).ooLog('debug',"push to callback stack, func: " + funct, "functions.js ADD");
Alan Moran's avatar
Alan Moran committed
}
//fxdiff FXOLAT-310 
var b_changedDomEl=new Array();
Alan Moran's avatar
Alan Moran committed

//same as above, but with a filter to prevent adding a funct. more than once
//funct then has to be an array("identifier", funct) 
Alan Moran's avatar
Alan Moran committed
function b_AddOnDomReplacementFinishedUniqueCallback(funct) {
	if (funct.constructor == Array){
		if(jQuery(document).ooLog().isDebugEnabled()) jQuery(document).ooLog('debug',"add: its an ARRAY! ", "functions.js ADD"); 
Alan Moran's avatar
Alan Moran committed
		//check if it has been added before
		if (b_onDomReplacementFinished_callbacks.search(funct[0])){
			if(jQuery(document).ooLog().isDebugEnabled()) jQuery(document).ooLog('debug',"push to callback stack, already there!!: " + funct[0], "functions.js ADD");		
Alan Moran's avatar
Alan Moran committed
			return;
		} 
	}
	b_AddOnDomReplacementFinishedCallback(funct);
}

// main interpreter for ajax mode
var o_debug_trid = 0;
function o_ainvoke(r) {
Alan Moran's avatar
Alan Moran committed
	// commands
Alan Moran's avatar
Alan Moran committed
	
	o_info.inainvoke = true;
	var cmdcnt = r["cmdcnt"];
	if (cmdcnt > 0) {
		// let everybody know dom replacement has finished
		jQuery(document).trigger("oo.dom.replacement.before");

		//fxdiff FXOLAT-310 
		b_changedDomEl = new Array();
		
Alan Moran's avatar
Alan Moran committed
		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
Alan Moran's avatar
Alan Moran committed
							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 inscripts = '';//jQuery(hfrag).find('script');//hfrag.extractScripts();
							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);
								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);
									}
								}
								
								if(civis) { // needed only for ie 6/7 bug where an empty div requires space on screen
									newc.css('display','');//.style.display="";//reset?
									newc.css('display','none'); //newc.style.display="none";
Alan Moran's avatar
Alan Moran committed
								}
								if(replaceElement || !withWrapper) {
									// replace entire DOM element 
										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;
										}
										if(window.console) console.log(e);
										if(window.console) console.log('Fragment',hdrco);
									b_changedDomEl.push(newcId);
Alan Moran's avatar
Alan Moran committed
								newc = null;
								
Alan Moran's avatar
Alan Moran committed
								if (inscripts != "") {
									inscripts.each( function(val){
										BLoader.executeGlobalJS(val, 'o_ainvoker::inscripts');}
									);
Alan Moran's avatar
Alan Moran committed
								}
								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"];
Alan Moran's avatar
Alan Moran committed
						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") {
Alan Moran's avatar
Alan Moran committed
								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);
Alan Moran's avatar
Alan Moran committed
							if (o_info.debug) o_log("c7: add js: "+url);
						}	
						break;	
					default:
						if (o_info.debug) o_log("?: unknown command "+co); 
						if(jQuery(document).ooLog().isDebugEnabled()) jQuery(document).ooLog('debug',"Error in o_ainvoke(), ?: unknown command "+co, "functions.js");
Alan Moran's avatar
Alan Moran committed
						break;
				}		
			} else {
				if (o_info.debug) o_log ("could not find window??");
				if(jQuery(document).ooLog().isDebugEnabled()) jQuery(document).ooLog('debug',"Error in o_ainvoke(), could not find window??", "functions.js");
Alan Moran's avatar
Alan Moran committed
		// execute onDomReplacementFinished callback functions
		var stacklength = b_onDomReplacementFinished_callbacks.length;
		if (b_onDomReplacementFinished_callbacks.toSource && jQuery(document).ooLog().isDebugEnabled()) { 
			jQuery(document).ooLog('debug',"stack content"+b_onDomReplacementFinished_callbacks.toSource(), "functions.js");
		for (mycounter = 0; stacklength > mycounter; mycounter++) {
			
Alan Moran's avatar
Alan Moran committed
			if (mycounter > 50) {
				if(jQuery(document).ooLog().isDebugEnabled()) {
					jQuery(document).ooLog('debug',"Stopped executing DOM replacement callback functions - to many functions::" + b_onDomReplacementFinished_callbacks.length, "functions.js");
				}
Alan Moran's avatar
Alan Moran committed
				break; // emergency break
			}
			if(jQuery(document).ooLog().isDebugEnabled()) {
				jQuery(document).ooLog('debug',"Stacksize before shift: " + b_onDomReplacementFinished_callbacks.length, "functions.js");
			}
Alan Moran's avatar
Alan Moran committed
			var func = b_onDomReplacementFinished_callbacks.shift();
			if (typeof func.length === 'number'){
				if (func[0] == "glosshighlighter") {
					var tmpArr = func[1];
					if(jQuery(document).ooLog().isDebugEnabled())
						jQuery(document).ooLog('debug',"arr fct: "+ tmpArr, "functions.js");
Alan Moran's avatar
Alan Moran committed
					func = tmpArr;
				 }				
			}
			if(jQuery(document).ooLog().isDebugEnabled())
				jQuery(document).ooLog('debug',"Executing DOM replacement callback function #" + mycounter + " with timeout funct::" + func, "functions.js");
Alan Moran's avatar
Alan Moran committed
			// don't use execScript here - must be executed outside this function scope so that dom replacement elements are available
			if(jQuery(document).ooLog().isDebugEnabled())
				jQuery(document).ooLog('debug',"Stacksize after timeout: " + b_onDomReplacementFinished_callbacks.length, "functions.js");
Alan Moran's avatar
Alan Moran committed
		}
		// END DEPRECATED DOM REPLACEMENT CALLBACK: new style on next line
		
		// let everybody know dom replacement has finished
		jQuery(document).trigger("oo.dom.replacement.after");
Alan Moran's avatar
Alan Moran committed
	}
	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();
	}
}

Alan Moran's avatar
Alan Moran committed
function showAjaxBusy() {
	// release o_info.linkbusy only after a successful server response 
	// - otherwhise the response gets overriden by next request
srosse's avatar
srosse committed
	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});
				}
srosse's avatar
srosse committed
			} catch (e) {
				if(window.console) console.log(e);
Alan Moran's avatar
Alan Moran committed
}

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

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);
	//TODO:gs:a why not directly accessing the submit button by an id. name="olat_fosm" send additional parameter which is unused. OLAT-1363
	if (myForm != null) {
		var mySubmit = myForm.olat_fosm_0;
		if(mySubmit == null){
			mySubmit = myForm.olat_fosm;
		}
		// set dirty css class
srosse's avatar
srosse committed
		if(mySubmit) mySubmit.className ="btn o_button_dirty";
	} else if(jQuery(document).ooLog().isDebugEnabled()) {
		jQuery(document).ooLog('debug',"Error in setFormDirty, myForm was null for formId=" + formId, "functions.js");
Alan Moran's avatar
Alan Moran committed
	}
}


//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();
Alan Moran's avatar
Alan Moran committed
}

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);
	}
	
Alan Moran's avatar
Alan Moran committed
	win.focus();
	if (o_info.linkbusy) {
		o_afterserver();
	}
Alan Moran's avatar
Alan Moran committed
}

function b_handleFileUploadFormChange(fileInputElement, fakeInputElement, saveButton) {
	// 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
	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) {
Alan Moran's avatar
Alan Moran committed
	}
	// 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();
		}
	}
}

// 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);
			} else if(jQuery(document).ooLog().isDebugEnabled()) {
				jQuery(document).ooLog('debug',"Error in gotonode(), could not find main window", "functions.js");
Alan Moran's avatar
Alan Moran committed
			}			
		}
	} catch (e) {
		alert('Goto node error:' + e);
		if(jQuery(document).ooLog().isDebugEnabled()) jQuery(document).ooLog('debug',"Error in gotonode()::" + e.message, "functions.js");
function o_viewportHeight() {
Alan Moran's avatar
Alan Moran committed
	// based on prototype library
	var prototypeViewPortHeight = jQuery(document).height()
	if (prototypeViewPortHeight > 0) {
		return prototypeViewPortHeight;
Alan Moran's avatar
Alan Moran committed
	} 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);
Alan Moran's avatar
Alan Moran committed
	mainInnerHeight = (mainInnerHeight > col3Height ? mainInnerHeight : col3Height);
	if (mainInnerHeight > 0) {
		return mainInnerHeight;
	} 
Alan Moran's avatar
Alan Moran committed
	// fallback, try to get height of main container
	mainDomElement = jQuery('#o_main');
	if (mainDomElement != 'undefined' && mainDomElement != null) { 
		mainHeight = mainDomElement.height();
Alan Moran's avatar
Alan Moran committed
	} 
	// fallback to viewport height	
	return o_viewportHeight();
	// Adjust the height of col1 2 and 3 based on the max column height. 
	// This is necessary to implement layouts where the three columns have different
	// backgounds and to enlarge the menu and content area to always show the whole 
	// content. It is also required by the left menu offcanvas feature.
		var contentHeight = 0;
		col1 = jQuery('#o_main_left_content').outerHeight(true);
		col2 = jQuery('#o_main_right_content').outerHeight(true);
		col3 = jQuery('#o_main_center_content').outerHeight(true);

		contentHeight = Math.max(col1, col2, col3);
		// Assign new col height
		if (col1 != null){
			jQuery('#o_main_left').css({'min-height' : contentHeight + "px"});
		if (col2 != null){
			jQuery('#o_main_right').css({'min-height' : contentHeight + "px"});
		}
		if (col3 != null){
			jQuery('#o_main_center').css({'min-height' : contentHeight + "px"});
		if(window.console)	console.log(e);			
/* 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);
		jQuery('html, body').animate({
			scrollTop : jQuery(elem).offset().top
		}, 333, function(e, el) {
			o_info.scrolling = false;
		});
function o_popover(id, contentId, loc) {
	if(typeof(loc)==='undefined') loc = 'bottom';
	jQuery('#' + id).popover({
    	placement : loc,
    	html: true,