/** * 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">×</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(); });