1 // Miscellaneous core Javascript functions for Moodle
2 // Global M object is initilised in inline javascript
5 * Add module to list of available modules that can be laoded from YUI.
6 * @param {Array} modules
8 M.yui.add_module = function(modules) {
9 for (var modname in modules) {
10 M.yui.loader.modules[modname] = modules[modname];
14 * The gallery version to use when loading YUI modules from the gallery.
15 * Will be changed every time when using local galleries.
17 M.yui.galleryversion = '2010.04.21-21-51';
20 * Various utility functions
22 M.util = M.util || {};
25 * Language strings - initialised from page footer.
30 * Returns url for images.
31 * @param {String} imagename
32 * @param {String} component
35 M.util.image_url = function(imagename, component) {
36 var url = M.cfg.wwwroot + '/theme/image.php?theme=' + M.cfg.theme + '&image=' + imagename;
38 if (M.cfg.themerev > 0) {
39 url = url + '&rev=' + M.cfg.themerev;
42 if (component && component != '' && component != 'moodle' && component != 'core') {
43 url = url + '&component=' + component;
49 M.util.in_array = function(item, array){
50 for( var i = 0; i<array.length; i++){
59 * Init a collapsible region, see print_collapsible_region in weblib.php
60 * @param {YUI} Y YUI3 instance with all libraries loaded
61 * @param {String} id the HTML id for the div.
62 * @param {String} userpref the user preference that records the state of this box. false if none.
63 * @param {String} strtooltip
65 M.util.init_collapsible_region = function(Y, id, userpref, strtooltip) {
66 Y.use('anim', function(Y) {
67 new M.util.CollapsibleRegion(Y, id, userpref, strtooltip);
72 * Object to handle a collapsible region : instantiate and forget styled object
76 * @param {YUI} Y YUI3 instance with all libraries loaded
77 * @param {String} id The HTML id for the div.
78 * @param {String} userpref The user preference that records the state of this box. false if none.
79 * @param {String} strtooltip
81 M.util.CollapsibleRegion = function(Y, id, userpref, strtooltip) {
82 // Record the pref name
83 this.userpref = userpref;
85 // Find the divs in the document.
86 this.div = Y.one('#'+id);
88 // Get the caption for the collapsible region
89 var caption = this.div.one('#'+id + '_caption');
92 var a = Y.Node.create('<a href="#"></a>');
93 a.setAttribute('title', strtooltip);
95 // Get all the nodes from caption, remove them and append them to <a>
96 while (caption.hasChildNodes()) {
97 child = caption.get('firstChild');
103 // Get the height of the div at this point before we shrink it if required
104 var height = this.div.get('offsetHeight');
105 if (this.div.hasClass('collapsed')) {
106 // Add the correct image and record the YUI node created in the process
107 this.icon = Y.Node.create('<img src="'+M.util.image_url('t/collapsed', 'moodle')+'" alt="" />');
108 // Shrink the div as it is collapsed by default
109 this.div.setStyle('height', caption.get('offsetHeight')+'px');
111 // Add the correct image and record the YUI node created in the process
112 this.icon = Y.Node.create('<img src="'+M.util.image_url('t/expanded', 'moodle')+'" alt="" />');
116 // Create the animation.
117 var animation = new Y.Anim({
120 easing: Y.Easing.easeBoth,
121 to: {height:caption.get('offsetHeight')},
122 from: {height:height}
125 // Handler for the animation finishing.
126 animation.on('end', function() {
127 this.div.toggleClass('collapsed');
128 if (this.div.hasClass('collapsed')) {
129 this.icon.set('src', M.util.image_url('t/collapsed', 'moodle'));
131 this.icon.set('src', M.util.image_url('t/expanded', 'moodle'));
135 // Hook up the event handler.
136 a.on('click', function(e, animation) {
138 // Animate to the appropriate size.
139 if (animation.get('running')) {
142 animation.set('reverse', this.div.hasClass('collapsed'));
143 // Update the user preference.
145 M.util.set_user_preference(this.userpref, !this.div.hasClass('collapsed'));
152 * The user preference that stores the state of this box.
156 M.util.CollapsibleRegion.prototype.userpref = null;
159 * The key divs that make up this
163 M.util.CollapsibleRegion.prototype.div = null;
166 * The key divs that make up this
170 M.util.CollapsibleRegion.prototype.icon = null;
173 * Makes a best effort to connect back to Moodle to update a user preference,
174 * however, there is no mechanism for finding out if the update succeeded.
176 * Before you can use this function in your JavsScript, you must have called
177 * user_preference_allow_ajax_update from moodlelib.php to tell Moodle that
178 * the udpate is allowed, and how to safely clean and submitted values.
180 * @param String name the name of the setting to udpate.
181 * @param String the value to set it to.
183 M.util.set_user_preference = function(name, value) {
184 YUI(M.yui.loader).use('io', function(Y) {
185 var url = M.cfg.wwwroot + '/lib/ajax/setuserpref.php?sesskey=' +
186 M.cfg.sesskey + '&pref=' + encodeURI(name) + '&value=' + encodeURI(value);
188 // If we are a developer, ensure that failures are reported.
193 if (M.cfg.developerdebug) {
194 cfg.on.failure = function(id, o, args) {
195 alert("Error updating user preference '" + name + "' using ajax. Clicking this link will repeat the Ajax call that failed so you can see the error: ");
205 * Prints a confirmation dialog in the style of DOM.confirm().
206 * @param object event A YUI DOM event or null if launched manually
207 * @param string message The message to show in the dialog
208 * @param string url The URL to forward to if YES is clicked. Disabled if fn is given
209 * @param function fn A JS function to run if YES is clicked.
211 M.util.show_confirm_dialog = function(e, args) {
212 var target = e.target;
213 if (e.preventDefault) {
217 YUI(M.yui.loader).use('yui2-container', 'yui2-event', function(Y) {
218 var simpledialog = new YAHOO.widget.SimpleDialog('confirmdialog',
227 simpledialog.setHeader(M.str.admin.confirmation);
228 simpledialog.setBody(args.message);
229 simpledialog.cfg.setProperty('icon', YAHOO.widget.SimpleDialog.ICON_WARN);
231 var handle_cancel = function() {
235 var handle_yes = function() {
239 // args comes from PHP, so callback will be a string, needs to be evaluated by JS
241 if (Y.Lang.isFunction(args.callback)) {
242 callback = args.callback;
244 callback = eval('('+args.callback+')');
247 if (Y.Lang.isObject(args.scope)) {
253 if (args.callbackargs) {
254 callback.apply(sc, args.callbackargs);
261 var targetancestor = null,
264 if (target.test('a')) {
265 window.location = target.get('href');
267 } else if ((targetancestor = target.ancestor('a')) !== null) {
268 window.location = targetancestor.get('href');
270 } else if (target.test('input')) {
271 targetform = target.ancestor(function(node) { return node.get('tagName').toLowerCase() == 'form'; });
272 // We cannot use target.ancestor('form') on the previous line
273 // because of http://yuilibrary.com/projects/yui3/ticket/2531561
277 if (target.get('name') && target.get('value')) {
278 targetform.append('<input type="hidden" name="' + target.get('name') +
279 '" value="' + target.get('value') + '">');
283 } else if (target.get('tagName').toLowerCase() == 'form') {
284 // We cannot use target.test('form') on the previous line because of
285 // http://yuilibrary.com/projects/yui3/ticket/2531561
288 } else if (M.cfg.developerdebug) {
289 alert("Element of type " + target.get('tagName') + " is not supported by the M.util.show_confirm_dialog function. Use A, INPUT or FORM");
293 if (!args.cancellabel) {
294 args.cancellabel = M.str.moodle.cancel;
296 if (!args.continuelabel) {
297 args.continuelabel = M.str.moodle.yes;
301 {text: args.cancellabel, handler: handle_cancel, isDefault: true},
302 {text: args.continuelabel, handler: handle_yes}
305 simpledialog.cfg.queueProperty('buttons', buttons);
307 simpledialog.render(document.body);
312 /** Useful for full embedding of various stuff */
313 M.util.init_maximised_embed = function(Y, id) {
314 var obj = Y.one('#'+id);
319 var get_htmlelement_size = function(el, prop) {
320 if (Y.Lang.isString(el)) {
321 el = Y.one('#' + el);
323 var val = el.getStyle(prop);
325 val = el.getComputedStyle(prop);
327 return parseInt(val);
330 var resize_object = function() {
331 obj.setStyle('width', '0px');
332 obj.setStyle('height', '0px');
333 var newwidth = get_htmlelement_size('maincontent', 'width') - 35;
335 if (newwidth > 500) {
336 obj.setStyle('width', newwidth + 'px');
338 obj.setStyle('width', '500px');
341 var headerheight = get_htmlelement_size('page-header', 'height');
342 var footerheight = get_htmlelement_size('page-footer', 'height');
343 var newheight = parseInt(YAHOO.util.Dom.getViewportHeight()) - footerheight - headerheight - 100;
344 if (newheight < 400) {
347 obj.setStyle('height', newheight+'px');
351 // fix layout if window resized too
352 window.onresize = function() {
358 * Attach handler to single_select
360 M.util.init_select_autosubmit = function(Y, formid, selectid, nothing) {
361 Y.use('event-key', function() {
362 var select = Y.one('#'+selectid);
364 // Try to get the form by id
365 var form = Y.one('#'+formid) || (function(){
366 // Hmmm the form's id may have been overriden by an internal input
367 // with the name id which will KILL IE.
368 // We need to manually iterate at this point because if the case
369 // above is true YUI's ancestor method will also kill IE!
371 while (form && form.get('nodeName').toUpperCase() !== 'FORM') {
372 form = form.ancestor();
376 // Make sure we have the form
378 // Create a function to handle our change event
379 var processchange = function(e, paramobject) {
380 if ((nothing===false || select.get('value') != nothing) && paramobject.lastindex != select.get('selectedIndex')) {
381 //prevent event bubbling and detach handlers to prevent multiple submissions caused by double clicking
383 paramobject.eventkeypress.detach();
384 paramobject.eventblur.detach();
385 paramobject.eventchangeorblur.detach();
390 // Attach the change event to the keypress, blur, and click actions.
391 // We don't use the change event because IE fires it on every arrow up/down
392 // event.... usability
393 var paramobject = new Object();
394 paramobject.lastindex = select.get('selectedIndex');
395 paramobject.eventkeypress = Y.on('key', processchange, select, 'press:13', form, paramobject);
396 paramobject.eventblur = select.on('blur', processchange, form, paramobject);
397 //little hack for chrome that need onChange event instead of onClick - see MDL-23224
399 paramobject.eventchangeorblur = select.on('change', processchange, form, paramobject);
401 paramobject.eventchangeorblur = select.on('click', processchange, form, paramobject);
409 * Attach handler to url_select
411 M.util.init_url_select = function(Y, formid, selectid, nothing) {
412 YUI(M.yui.loader).use('node', function(Y) {
413 Y.on('change', function() {
414 if ((nothing == false && Y.Lang.isBoolean(nothing)) || Y.one('#'+selectid).get('value') != nothing) {
415 window.location = M.cfg.wwwroot+Y.one('#'+selectid).get('value');
423 * Breaks out all links to the top frame - used in frametop page layout.
425 M.util.init_frametop = function(Y) {
426 Y.all('a').each(function(node) {
427 node.set('target', '_top');
429 Y.all('form').each(function(node) {
430 node.set('target', '_top');
435 * Finds all nodes that match the given CSS selector and attaches events to them
436 * so that they toggle a given classname when clicked.
439 * @param {string} id An id containing elements to target
440 * @param {string} cssselector A selector to use to find targets
441 * @param {string} toggleclassname A classname to toggle
443 M.util.init_toggle_class_on_click = function(Y, id, cssselector, toggleclassname, togglecssselector) {
445 if (togglecssselector == '') {
446 togglecssselector = cssselector;
449 var node = Y.one('#'+id);
450 node.all(cssselector).each(function(n){
451 n.on('click', function(e){
453 if (e.target.test(cssselector) && !e.target.test('a') && !e.target.test('img')) {
454 if (this.test(togglecssselector)) {
455 this.toggleClass(toggleclassname);
457 this.ancestor(togglecssselector).toggleClass(toggleclassname);
462 // Attach this click event to the node rather than all selectors... will be much better
464 node.on('click', function(e){
465 if (e.target.hasClass('addtoall')) {
466 this.all(togglecssselector).addClass(toggleclassname);
467 } else if (e.target.hasClass('removefromall')) {
468 this.all(togglecssselector+'.'+toggleclassname).removeClass(toggleclassname);
474 * Initialises a colour picker
476 * Designed to be used with admin_setting_configcolourpicker although could be used
477 * anywhere, just give a text input an id and insert a div with the class admin_colourpicker
478 * above or below the input (must have the same parent) and then call this with the
481 * This code was mostly taken from my [Sam Hemelryk] css theme tool available in
482 * contrib/blocks. For better docs refer to that.
486 * @param {object} previewconf
488 M.util.init_colour_picker = function(Y, id, previewconf) {
490 * We need node and event-mouseenter
492 Y.use('node', 'event-mouseenter', function(){
494 * The colour picker object
503 eventMouseEnter : null,
504 eventMouseLeave : null,
505 eventMouseMove : null,
510 * Initalises the colour picker by putting everything together and wiring the events
513 this.input = Y.one('#'+id);
514 this.box = this.input.ancestor().one('.admin_colourpicker');
515 this.image = Y.Node.create('<img alt="" class="colourdialogue" />');
516 this.image.setAttribute('src', M.util.image_url('i/colourpicker', 'moodle'));
517 this.preview = Y.Node.create('<div class="previewcolour"></div>');
518 this.preview.setStyle('width', this.height/2).setStyle('height', this.height/2).setStyle('backgroundColor', this.input.get('value'));
519 this.current = Y.Node.create('<div class="currentcolour"></div>');
520 this.current.setStyle('width', this.height/2).setStyle('height', this.height/2 -1).setStyle('backgroundColor', this.input.get('value'));
521 this.box.setContent('').append(this.image).append(this.preview).append(this.current);
523 if (typeof(previewconf) === 'object' && previewconf !== null) {
524 Y.one('#'+id+'_preview').on('click', function(e){
525 if (Y.Lang.isString(previewconf.selector)) {
526 Y.all(previewconf.selector).setStyle(previewconf.style, this.input.get('value'));
528 for (var i in previewconf.selector) {
529 Y.all(previewconf.selector[i]).setStyle(previewconf.style, this.input.get('value'));
535 this.eventClick = this.image.on('click', this.pickColour, this);
536 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
539 * Starts to follow the mouse once it enter the image
541 startFollow : function(e) {
542 this.eventMouseEnter.detach();
543 this.eventMouseLeave = Y.on('mouseleave', this.endFollow, this.image, this);
544 this.eventMouseMove = this.image.on('mousemove', function(e){
545 this.preview.setStyle('backgroundColor', this.determineColour(e));
549 * Stops following the mouse
551 endFollow : function(e) {
552 this.eventMouseMove.detach();
553 this.eventMouseLeave.detach();
554 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
557 * Picks the colour the was clicked on
559 pickColour : function(e) {
560 var colour = this.determineColour(e);
561 this.input.set('value', colour);
562 this.current.setStyle('backgroundColor', colour);
565 * Calculates the colour fromthe given co-ordinates
567 determineColour : function(e) {
568 var eventx = Math.floor(e.pageX-e.target.getX());
569 var eventy = Math.floor(e.pageY-e.target.getY());
571 var imagewidth = this.width;
572 var imageheight = this.height;
573 var factor = this.factor;
574 var colour = [255,0,0];
585 var matrixcount = matrices.length;
586 var limit = Math.round(imagewidth/matrixcount);
587 var heightbreak = Math.round(imageheight/2);
589 for (var x = 0; x < imagewidth; x++) {
590 var divisor = Math.floor(x / limit);
591 var matrix = matrices[divisor];
593 colour[0] += matrix[0]*factor;
594 colour[1] += matrix[1]*factor;
595 colour[2] += matrix[2]*factor;
602 var pixel = [colour[0], colour[1], colour[2]];
603 if (eventy < heightbreak) {
604 pixel[0] += Math.floor(((255-pixel[0])/heightbreak) * (heightbreak - eventy));
605 pixel[1] += Math.floor(((255-pixel[1])/heightbreak) * (heightbreak - eventy));
606 pixel[2] += Math.floor(((255-pixel[2])/heightbreak) * (heightbreak - eventy));
607 } else if (eventy > heightbreak) {
608 pixel[0] = Math.floor((imageheight-eventy)*(pixel[0]/heightbreak));
609 pixel[1] = Math.floor((imageheight-eventy)*(pixel[1]/heightbreak));
610 pixel[2] = Math.floor((imageheight-eventy)*(pixel[2]/heightbreak));
613 return this.convert_rgb_to_hex(pixel);
616 * Converts an RGB value to Hex
618 convert_rgb_to_hex : function(rgb) {
620 var hexchars = "0123456789ABCDEF";
621 for (var i=0; i<3; i++) {
622 var number = Math.abs(rgb[i]);
623 if (number == 0 || isNaN(number)) {
626 hex += hexchars.charAt((number-number%16)/16)+hexchars.charAt(number%16);
633 * Initialise the colour picker :) Hoorah
639 M.util.init_block_hider = function(Y, config) {
640 Y.use('base', 'node', function(Y) {
641 M.util.block_hider = M.util.block_hider || (function(){
642 var blockhider = function() {
643 blockhider.superclass.constructor.apply(this, arguments);
645 blockhider.prototype = {
646 initializer : function(config) {
647 this.set('block', '#'+this.get('id'));
648 var b = this.get('block'),
651 if (t && (a = t.one('.block_action'))) {
652 var hide = Y.Node.create('<img class="block-hider-hide" tabindex="0" alt="'+config.tooltipVisible+'" title="'+config.tooltipVisible+'" />');
653 hide.setAttribute('src', this.get('iconVisible')).on('click', this.updateState, this, true);
654 hide.on('keypress', this.updateStateKey, this, true);
655 var show = Y.Node.create('<img class="block-hider-show" tabindex="0" alt="'+config.tooltipHidden+'" title="'+config.tooltipHidden+'" />');
656 show.setAttribute('src', this.get('iconHidden')).on('click', this.updateState, this, false);
657 show.on('keypress', this.updateStateKey, this, false);
658 a.insert(show, 0).insert(hide, 0);
661 updateState : function(e, hide) {
662 M.util.set_user_preference(this.get('preference'), hide);
664 this.get('block').addClass('hidden');
666 this.get('block').removeClass('hidden');
669 updateStateKey : function(e, hide) {
670 if (e.keyCode == 13) { //allow hide/show via enter key
671 this.updateState(this, hide);
675 Y.extend(blockhider, Y.Base, blockhider.prototype, {
681 value : M.util.image_url('t/switch_minus', 'moodle')
684 value : M.util.image_url('t/switch_plus', 'moodle')
687 setter : function(node) {
695 new M.util.block_hider(config);
700 * Returns a string registered in advance for usage in JavaScript
702 * If you do not pass the third parameter, the function will just return
703 * the corresponding value from the M.str object. If the third parameter is
704 * provided, the function performs {$a} placeholder substitution in the
705 * same way as PHP get_string() in Moodle does.
707 * @param {String} identifier string identifier
708 * @param {String} component the component providing the string
709 * @param {Object|String} a optional variable to populate placeholder with
711 M.util.get_string = function(identifier, component, a) {
714 if (M.cfg.developerdebug) {
715 // creating new instance if YUI is not optimal but it seems to be better way then
716 // require the instance via the function API - note that it is used in rare cases
717 // for debugging only anyway
718 // To ensure we don't kill browser performance if hundreds of get_string requests
719 // are made we cache the instance we generate within the M.util namespace.
720 // We don't publicly define the variable so that it doesn't get abused.
721 if (typeof M.util.get_string_yui_instance === 'undefined') {
722 M.util.get_string_yui_instance = new YUI({ debug : true });
724 var Y = M.util.get_string_yui_instance;
727 if (!M.str.hasOwnProperty(component) || !M.str[component].hasOwnProperty(identifier)) {
728 stringvalue = '[[' + identifier + ',' + component + ']]';
729 if (M.cfg.developerdebug) {
730 Y.log('undefined string ' + stringvalue, 'warn', 'M.util.get_string');
735 stringvalue = M.str[component][identifier];
737 if (typeof a == 'undefined') {
738 // no placeholder substitution requested
742 if (typeof a == 'number' || typeof a == 'string') {
743 // replace all occurrences of {$a} with the placeholder value
744 stringvalue = stringvalue.replace(/\{\$a\}/g, a);
748 if (typeof a == 'object') {
749 // replace {$a->key} placeholders
751 if (typeof a[key] != 'number' && typeof a[key] != 'string') {
752 if (M.cfg.developerdebug) {
753 Y.log('invalid value type for $a->' + key, 'warn', 'M.util.get_string');
757 var search = '{$a->' + key + '}';
758 search = search.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
759 search = new RegExp(search, 'g');
760 stringvalue = stringvalue.replace(search, a[key]);
765 if (M.cfg.developerdebug) {
766 Y.log('incorrect placeholder type', 'warn', 'M.util.get_string');
772 * Set focus on username or password field of the login form
774 M.util.focus_login_form = function(Y) {
775 var username = Y.one('#username');
776 var password = Y.one('#password');
778 if (username == null || password == null) {
779 // something is wrong here
783 var curElement = document.activeElement
784 if (curElement == 'undefined') {
785 // legacy browser - skip refocus protection
786 } else if (curElement.tagName == 'INPUT') {
787 // user was probably faster to focus something, do not mess with focus
791 if (username.get('value') == '') {
799 //=== old legacy JS code, hopefully to be replaced soon by M.xx.yy and YUI3 code ===
801 function checkall() {
802 var inputs = document.getElementsByTagName('input');
803 for (var i = 0; i < inputs.length; i++) {
804 if (inputs[i].type == 'checkbox') {
805 if (inputs[i].disabled || inputs[i].readOnly) {
808 inputs[i].checked = true;
813 function checknone() {
814 var inputs = document.getElementsByTagName('input');
815 for (var i = 0; i < inputs.length; i++) {
816 if (inputs[i].type == 'checkbox') {
817 if (inputs[i].disabled || inputs[i].readOnly) {
820 inputs[i].checked = false;
826 * Either check, or uncheck, all checkboxes inside the element with id is
827 * @param id the id of the container
828 * @param checked the new state, either '' or 'checked'.
830 function select_all_in_element_with_id(id, checked) {
831 var container = document.getElementById(id);
835 var inputs = container.getElementsByTagName('input');
836 for (var i = 0; i < inputs.length; ++i) {
837 if (inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
838 inputs[i].checked = checked;
843 function select_all_in(elTagName, elClass, elId) {
844 var inputs = document.getElementsByTagName('input');
845 inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
846 for(var i = 0; i < inputs.length; ++i) {
847 if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
848 inputs[i].checked = 'checked';
853 function deselect_all_in(elTagName, elClass, elId) {
854 var inputs = document.getElementsByTagName('INPUT');
855 inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
856 for(var i = 0; i < inputs.length; ++i) {
857 if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
858 inputs[i].checked = '';
863 function confirm_if(expr, message) {
867 return confirm(message);
872 findParentNode (start, elementName, elementClass, elementID)
874 Travels up the DOM hierarchy to find a parent element with the
875 specified tag name, class, and id. All conditions must be met,
876 but any can be ommitted. Returns the BODY element if no match
879 function findParentNode(el, elName, elClass, elId) {
880 while (el.nodeName.toUpperCase() != 'BODY') {
881 if ((!elName || el.nodeName.toUpperCase() == elName) &&
882 (!elClass || el.className.indexOf(elClass) != -1) &&
883 (!elId || el.id == elId)) {
891 findChildNode (start, elementName, elementClass, elementID)
893 Travels down the DOM hierarchy to find all child elements with the
894 specified tag name, class, and id. All conditions must be met,
895 but any can be ommitted.
896 Doesn't examine children of matches.
898 function findChildNodes(start, tagName, elementClass, elementID, elementName) {
899 var children = new Array();
900 for (var i = 0; i < start.childNodes.length; i++) {
901 var classfound = false;
902 var child = start.childNodes[i];
903 if((child.nodeType == 1) &&//element node type
904 (elementClass && (typeof(child.className)=='string'))) {
905 var childClasses = child.className.split(/\s+/);
906 for (var childClassIndex in childClasses) {
907 if (childClasses[childClassIndex]==elementClass) {
913 if(child.nodeType == 1) { //element node type
914 if ( (!tagName || child.nodeName == tagName) &&
915 (!elementClass || classfound)&&
916 (!elementID || child.id == elementID) &&
917 (!elementName || child.name == elementName))
919 children = children.concat(child);
921 children = children.concat(findChildNodes(child, tagName, elementClass, elementID, elementName));
928 function unmaskPassword(id) {
929 var pw = document.getElementById(id);
930 var chb = document.getElementById(id+'unmask');
933 // first try IE way - it can not set name attribute later
935 var newpw = document.createElement('<input type="text" autocomplete="off" name="'+pw.name+'">');
937 var newpw = document.createElement('<input type="password" autocomplete="off" name="'+pw.name+'">');
939 newpw.attributes['class'].nodeValue = pw.attributes['class'].nodeValue;
941 var newpw = document.createElement('input');
942 newpw.setAttribute('autocomplete', 'off');
943 newpw.setAttribute('name', pw.name);
945 newpw.setAttribute('type', 'text');
947 newpw.setAttribute('type', 'password');
949 newpw.setAttribute('class', pw.getAttribute('class'));
952 newpw.size = pw.size;
953 newpw.onblur = pw.onblur;
954 newpw.onchange = pw.onchange;
955 newpw.value = pw.value;
956 pw.parentNode.replaceChild(newpw, pw);
959 function filterByParent(elCollection, parentFinder) {
960 var filteredCollection = [];
961 for (var i = 0; i < elCollection.length; ++i) {
962 var findParent = parentFinder(elCollection[i]);
963 if (findParent.nodeName.toUpperCase() != 'BODY') {
964 filteredCollection.push(elCollection[i]);
967 return filteredCollection;
971 All this is here just so that IE gets to handle oversized blocks
972 in a visually pleasing manner. It does a browser detect. So sue me.
975 function fix_column_widths() {
976 var agt = navigator.userAgent.toLowerCase();
977 if ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1)) {
978 fix_column_width('left-column');
979 fix_column_width('right-column');
983 function fix_column_width(colName) {
984 if(column = document.getElementById(colName)) {
985 if(!column.offsetWidth) {
986 setTimeout("fix_column_width('" + colName + "')", 20);
991 var nodes = column.childNodes;
993 for(i = 0; i < nodes.length; ++i) {
994 if(nodes[i].className.indexOf("block") != -1 ) {
995 if(width < nodes[i].offsetWidth) {
996 width = nodes[i].offsetWidth;
1001 for(i = 0; i < nodes.length; ++i) {
1002 if(nodes[i].className.indexOf("block") != -1 ) {
1003 nodes[i].style.width = width + 'px';
1011 Insert myValue at current cursor position
1013 function insertAtCursor(myField, myValue) {
1015 if (document.selection) {
1017 sel = document.selection.createRange();
1020 // Mozilla/Netscape support
1021 else if (myField.selectionStart || myField.selectionStart == '0') {
1022 var startPos = myField.selectionStart;
1023 var endPos = myField.selectionEnd;
1024 myField.value = myField.value.substring(0, startPos)
1025 + myValue + myField.value.substring(endPos, myField.value.length);
1027 myField.value += myValue;
1033 Call instead of setting window.onload directly or setting body onload=.
1034 Adds your function to a chain of functions rather than overwriting anything
1037 function addonload(fn) {
1038 var oldhandler=window.onload;
1039 window.onload=function() {
1040 if(oldhandler) oldhandler();
1045 * Replacement for getElementsByClassName in browsers that aren't cool enough
1047 * Relying on the built-in getElementsByClassName is far, far faster than
1050 * Note: the third argument used to be an object with odd behaviour. It now
1051 * acts like the 'name' in the HTML5 spec, though the old behaviour is still
1052 * mimicked if you pass an object.
1054 * @param {Node} oElm The top-level node for searching. To search a whole
1055 * document, use `document`.
1056 * @param {String} strTagName filter by tag names
1057 * @param {String} name same as HTML5 spec
1059 function getElementsByClassName(oElm, strTagName, name) {
1060 // for backwards compatibility
1061 if(typeof name == "object") {
1062 var names = new Array();
1063 for(var i=0; i<name.length; i++) names.push(names[i]);
1064 name = names.join('');
1066 // use native implementation if possible
1067 if (oElm.getElementsByClassName && Array.filter) {
1068 if (strTagName == '*') {
1069 return oElm.getElementsByClassName(name);
1071 return Array.filter(oElm.getElementsByClassName(name), function(el) {
1072 return el.nodeName.toLowerCase() == strTagName.toLowerCase();
1076 // native implementation unavailable, fall back to slow method
1077 var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
1078 var arrReturnElements = new Array();
1079 var arrRegExpClassNames = new Array();
1080 var names = name.split(' ');
1081 for(var i=0; i<names.length; i++) {
1082 arrRegExpClassNames.push(new RegExp("(^|\\s)" + names[i].replace(/\-/g, "\\-") + "(\\s|$)"));
1086 for(var j=0; j<arrElements.length; j++) {
1087 oElement = arrElements[j];
1089 for(var k=0; k<arrRegExpClassNames.length; k++) {
1090 if(!arrRegExpClassNames[k].test(oElement.className)) {
1091 bMatchesAll = false;
1096 arrReturnElements.push(oElement);
1099 return (arrReturnElements)
1102 function openpopup(event, args) {
1105 if (event.preventDefault) {
1106 event.preventDefault();
1108 event.returnValue = false;
1112 // Cleans window name because IE does not support funky ones.
1113 var nameregex = /[^a-z0-9_]/i;
1114 if (args.name.match(nameregex)) {
1115 args.name = args.name.replace(nameregex, '_');
1116 if (M.cfg.developerdebug) {
1117 alert('DEVELOPER NOTICE: Invalid \'name\' passed to openpopup()');
1121 var fullurl = args.url;
1122 if (!args.url.match(/https?:\/\//)) {
1123 fullurl = M.cfg.wwwroot + args.url;
1125 if (args.fullscreen) {
1126 args.options = args.options.
1127 replace(/top=\d+/, 'top=0').
1128 replace(/left=\d+/, 'left=0').
1129 replace(/width=\d+/, 'width=' + screen.availWidth).
1130 replace(/height=\d+/, 'height=' + screen.availHeight);
1132 var windowobj = window.open(fullurl,args.name,args.options);
1137 if (args.fullscreen) {
1138 // In some browser / OS combinations (E.g. Chrome on Windows), the
1139 // window initially opens slighly too big. The width and heigh options
1140 // seem to control the area inside the browser window, so what with
1141 // scroll-bars, etc. the actual window is bigger than the screen.
1142 // Therefore, we need to fix things up after the window is open.
1143 var hackcount = 100;
1144 var get_size_exactly_right = function() {
1145 windowobj.moveTo(0, 0);
1146 windowobj.resizeTo(screen.availWidth, screen.availHeight);
1148 // Unfortunately, it seems that in Chrome on Ubuntu, if you call
1149 // something like windowobj.resizeTo(1280, 1024) too soon (up to
1150 // about 50ms) after the window is open, then it actually behaves
1151 // as if you called windowobj.resizeTo(0, 0). Therefore, we need to
1152 // check that the resize actually worked, and if not, repeatedly try
1153 // again after a short delay until it works (but with a limit of
1154 // hackcount repeats.
1155 if (hackcount > 0 && (windowobj.innerHeight < 10 || windowobj.innerWidth < 10)) {
1157 setTimeout(get_size_exactly_right, 10);
1160 setTimeout(get_size_exactly_right, 0);
1167 /** Close the current browser window. */
1168 function close_window(e) {
1169 if (e.preventDefault) {
1172 e.returnValue = false;
1178 * Used in a couple of modules to hide navigation areas when using AJAX
1181 function show_item(itemid) {
1182 var item = document.getElementById(itemid);
1184 item.style.display = "";
1188 function destroy_item(itemid) {
1189 var item = document.getElementById(itemid);
1191 item.parentNode.removeChild(item);
1195 * Tranfer keyboard focus to the HTML element with the given id, if it exists.
1196 * @param controlid the control id.
1198 function focuscontrol(controlid) {
1199 var control = document.getElementById(controlid);
1206 * Transfers keyboard focus to an HTML element based on the old style style of focus
1207 * This function should be removed as soon as it is no longer used
1209 function old_onload_focus(formid, controlname) {
1210 if (document.forms[formid] && document.forms[formid].elements && document.forms[formid].elements[controlname]) {
1211 document.forms[formid].elements[controlname].focus();
1215 function build_querystring(obj) {
1216 return convert_object_to_string(obj, '&');
1219 function build_windowoptionsstring(obj) {
1220 return convert_object_to_string(obj, ',');
1223 function convert_object_to_string(obj, separator) {
1224 if (typeof obj !== 'object') {
1229 k = encodeURIComponent(k);
1231 if(obj[k] instanceof Array) {
1232 for(var i in value) {
1233 list.push(k+'[]='+encodeURIComponent(value[i]));
1236 list.push(k+'='+encodeURIComponent(value));
1239 return list.join(separator);
1242 function stripHTML(str) {
1243 var re = /<\S[^><]*>/g;
1244 var ret = str.replace(re, "");
1248 Number.prototype.fixed=function(n){
1250 return round(Number(this)*pow(10,n))/pow(10,n);
1252 function update_progress_bar (id, width, pt, msg, es){
1254 var status = document.getElementById("status_"+id);
1255 var percent_indicator = document.getElementById("pt_"+id);
1256 var progress_bar = document.getElementById("progress_"+id);
1257 var time_es = document.getElementById("time_"+id);
1258 status.innerHTML = msg;
1259 percent_indicator.innerHTML = percent.fixed(2) + '%';
1260 if(percent == 100) {
1261 progress_bar.style.background = "green";
1262 time_es.style.display = "none";
1264 progress_bar.style.background = "#FFCC66";
1266 time_es.innerHTML = "";
1268 time_es.innerHTML = es.fixed(2)+" sec";
1269 time_es.style.display
1273 progress_bar.style.width = width + "px";
1278 // ===== Deprecated core Javascript functions for Moodle ====
1279 // DO NOT USE!!!!!!!
1280 // Do not put this stuff in separate file because it only adds extra load on servers!
1283 * Used in a couple of modules to hide navigation areas when using AJAX
1285 function hide_item(itemid) {
1286 // use class='hiddenifjs' instead
1287 var item = document.getElementById(itemid);
1289 item.style.display = "none";
1293 M.util.help_icon = {
1296 add : function(Y, properties) {
1298 properties.node = Y.one('#'+properties.id);
1299 if (properties.node) {
1300 properties.node.on('click', this.display, this, properties);
1303 display : function(event, args) {
1304 event.preventDefault();
1305 if (M.util.help_icon.instance === null) {
1306 var Y = M.util.help_icon.Y;
1307 Y.use('overlay', 'io-base', 'event-mouseenter', 'node', 'event-key', function(Y) {
1308 var help_content_overlay = {
1313 var closebtn = Y.Node.create('<a id="closehelpbox" href="#"><img src="'+M.util.image_url('t/delete', 'moodle')+'" /></a>');
1314 // Create an overlay from markup
1315 this.overlay = new Y.Overlay({
1316 headerContent: closebtn,
1323 this.overlay.render(Y.one(document.body));
1325 closebtn.on('click', this.overlay.hide, this.overlay);
1327 var boundingBox = this.overlay.get("boundingBox");
1329 // Hide the menu if the user clicks outside of its content
1330 boundingBox.get("ownerDocument").on("mousedown", function (event) {
1331 var oTarget = event.target;
1332 var menuButton = Y.one("#"+args.id);
1334 if (!oTarget.compareTo(menuButton) &&
1335 !menuButton.contains(oTarget) &&
1336 !oTarget.compareTo(boundingBox) &&
1337 !boundingBox.contains(oTarget)) {
1338 this.overlay.hide();
1342 Y.on("key", this.close, closebtn , "down:13", this);
1343 closebtn.on('click', this.close, this);
1346 close : function(e) {
1348 this.helplink.focus();
1349 this.overlay.hide();
1352 display : function(event, args) {
1353 this.helplink = args.node;
1354 this.overlay.set('bodyContent', Y.Node.create('<img src="'+M.cfg.loadingicon+'" class="spinner" />'));
1355 this.overlay.set("align", {node:args.node, points:[Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.RC]});
1357 var fullurl = args.url;
1358 if (!args.url.match(/https?:\/\//)) {
1359 fullurl = M.cfg.wwwroot + args.url;
1362 var ajaxurl = fullurl + '&ajax=1';
1368 success: function(id, o, node) {
1369 this.display_callback(o.responseText);
1371 failure: function(id, o, node) {
1372 var debuginfo = o.statusText;
1373 if (M.cfg.developerdebug) {
1374 o.statusText += ' (' + ajaxurl + ')';
1376 this.display_callback('bodyContent',debuginfo);
1382 this.overlay.show();
1384 Y.one('#closehelpbox').focus();
1387 display_callback : function(content) {
1388 content = '<div role="alert">' + content + '</div>';
1389 this.overlay.set('bodyContent', content);
1392 hideContent : function() {
1394 help.overlay.hide();
1397 help_content_overlay.init();
1398 M.util.help_icon.instance = help_content_overlay;
1399 M.util.help_icon.instance.display(event, args);
1402 M.util.help_icon.instance.display(event, args);
1405 init : function(Y) {
1411 * Custom menu namespace
1413 M.core_custom_menu = {
1415 * This method is used to initialise a custom menu given the id that belongs
1416 * to the custom menu's root node.
1419 * @param {string} nodeid
1421 init : function(Y, nodeid) {
1422 var node = Y.one('#'+nodeid);
1424 Y.use('node-menunav', function(Y) {
1426 // Remove the javascript-disabled class.... obviously javascript is enabled.
1427 node.removeClass('javascript-disabled');
1428 // Initialise the menunav plugin
1429 node.plug(Y.Plugin.NodeMenuNav);
1436 * Used to store form manipulation methods and enhancments
1438 M.form = M.form || {};
1441 * Converts a nbsp indented select box into a multi drop down custom control much
1442 * like the custom menu. It also selectable categories on or off.
1444 * $form->init_javascript_enhancement('elementname','smartselect', array('selectablecategories'=>true|false, 'mode'=>'compact'|'spanning'));
1447 * @param {string} id
1448 * @param {Array} options
1450 M.form.init_smartselect = function(Y, id, options) {
1451 if (!id.match(/^id_/)) {
1454 var select = Y.one('select#'+id);
1458 Y.use('event-delegate',function(){
1464 currentvalue : null,
1468 selectablecategories : true,
1476 init : function(Y, id, args, nodes) {
1477 if (typeof(args)=='object') {
1478 for (var i in this.cfg) {
1479 if (args[i] || args[i]===false) {
1480 this.cfg[i] = args[i];
1485 // Display a loading message first up
1486 this.nodes.select = nodes.select;
1488 this.currentvalue = this.nodes.select.get('selectedIndex');
1489 this.currenttext = this.nodes.select.all('option').item(this.currentvalue).get('innerHTML');
1491 var options = Array();
1492 options[''] = {text:this.currenttext,value:'',depth:0,children:[]};
1493 this.nodes.select.all('option').each(function(option, index) {
1494 var rawtext = option.get('innerHTML');
1495 var text = rawtext.replace(/^( )*/, '');
1496 if (rawtext === text) {
1497 text = rawtext.replace(/^(\s)*/, '');
1498 var depth = (rawtext.length - text.length ) + 1;
1500 var depth = ((rawtext.length - text.length )/12)+1;
1502 option.set('innerHTML', text);
1503 options['i'+index] = {text:text,depth:depth,index:index,children:[]};
1506 this.structure = [];
1507 var structcount = 0;
1508 for (var i in options) {
1511 this.structure.push(o);
1515 var current = this.structure[structcount-1];
1516 for (var j = 0; j < o.depth-1;j++) {
1517 if (current && current.children) {
1518 current = current.children[current.children.length-1];
1521 if (current && current.children) {
1522 current.children.push(o);
1527 this.nodes.menu = Y.Node.create(this.generate_menu_content());
1528 this.nodes.menu.one('.smartselect_mask').setStyle('opacity', 0.01);
1529 this.nodes.menu.one('.smartselect_mask').setStyle('width', (this.nodes.select.get('offsetWidth')+5)+'px');
1530 this.nodes.menu.one('.smartselect_mask').setStyle('height', (this.nodes.select.get('offsetHeight'))+'px');
1532 if (this.cfg.mode == null) {
1533 var formwidth = this.nodes.select.ancestor('form').get('offsetWidth');
1534 if (formwidth < 400 || this.nodes.menu.get('offsetWidth') < formwidth*2) {
1535 this.cfg.mode = 'compact';
1537 this.cfg.mode = 'spanning';
1541 if (this.cfg.mode == 'compact') {
1542 this.nodes.menu.addClass('compactmenu');
1544 this.nodes.menu.addClass('spanningmenu');
1545 this.nodes.menu.delegate('mouseover', this.show_sub_menu, '.smartselect_submenuitem', this);
1548 Y.one(document.body).append(this.nodes.menu);
1549 var pos = this.nodes.select.getXY();
1551 this.nodes.menu.setXY(pos);
1552 this.nodes.menu.on('click', this.handle_click, this);
1554 Y.one(window).on('resize', function(){
1555 var pos = this.nodes.select.getXY();
1557 this.nodes.menu.setXY(pos);
1560 generate_menu_content : function() {
1561 var content = '<div id="'+this.id+'_smart_select" class="smartselect">';
1562 content += this.generate_submenu_content(this.structure[0], true);
1563 content += '</ul></div>';
1566 generate_submenu_content : function(item, rootelement) {
1567 this.submenucount++;
1569 if (item.children.length > 0) {
1571 content += '<div class="smartselect_mask" href="#ss_submenu'+this.submenucount+'"> </div>';
1572 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_menu">';
1573 content += '<div class="smartselect_menu_content">';
1575 content += '<li class="smartselect_submenuitem">';
1576 var categoryclass = (this.cfg.selectablecategories)?'selectable':'notselectable';
1577 content += '<a class="smartselect_menuitem_label '+categoryclass+'" href="#ss_submenu'+this.submenucount+'" value="'+item.index+'">'+item.text+'</a>';
1578 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_submenu">';
1579 content += '<div class="smartselect_submenu_content">';
1582 for (var i in item.children) {
1583 content += this.generate_submenu_content(item.children[i],false);
1586 content += '</div>';
1587 content += '</div>';
1593 content += '<li class="smartselect_menuitem">';
1594 content += '<a class="smartselect_menuitem_content selectable" href="#" value="'+item.index+'">'+item.text+'</a>';
1599 select : function(e) {
1602 this.currenttext = t.get('innerHTML');
1603 this.currentvalue = t.getAttribute('value');
1604 this.nodes.select.set('selectedIndex', this.currentvalue);
1607 handle_click : function(e) {
1608 var target = e.target;
1609 if (target.hasClass('smartselect_mask')) {
1611 } else if (target.hasClass('selectable') || target.hasClass('smartselect_menuitem')) {
1613 } else if (target.hasClass('smartselect_menuitem_label') || target.hasClass('smartselect_submenuitem')) {
1614 this.show_sub_menu(e);
1617 show_menu : function(e) {
1619 var menu = e.target.ancestor().one('.smartselect_menu');
1620 menu.addClass('visible');
1621 this.shownevent = Y.one(document.body).on('click', this.hide_menu, this);
1623 show_sub_menu : function(e) {
1625 var target = e.target;
1626 if (!target.hasClass('smartselect_submenuitem')) {
1627 target = target.ancestor('.smartselect_submenuitem');
1629 if (this.cfg.mode == 'compact' && target.one('.smartselect_submenu').hasClass('visible')) {
1630 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1633 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1634 target.one('.smartselect_submenu').addClass('visible');
1636 hide_menu : function() {
1637 this.nodes.menu.all('.visible').removeClass('visible');
1638 if (this.shownevent) {
1639 this.shownevent.detach();
1643 smartselect.init(Y, id, options, {select:select});
1647 /** List of flv players to be loaded */
1648 M.util.video_players = [];
1649 /** List of mp3 players to be loaded */
1650 M.util.audio_players = [];
1654 * @param id element id
1655 * @param fileurl media url
1658 * @param autosize true means detect size from media
1660 M.util.add_video_player = function (id, fileurl, width, height, autosize) {
1661 M.util.video_players.push({id: id, fileurl: fileurl, width: width, height: height, autosize: autosize, resized: false});
1670 M.util.add_audio_player = function (id, fileurl, small) {
1671 M.util.audio_players.push({id: id, fileurl: fileurl, small: small});
1675 * Initialise all audio and video player, must be called from page footer.
1677 M.util.load_flowplayer = function() {
1678 if (M.util.video_players.length == 0 && M.util.audio_players.length == 0) {
1681 if (typeof(flowplayer) == 'undefined') {
1684 var embed_function = function() {
1685 if (loaded || typeof(flowplayer) == 'undefined') {
1693 /* TODO: add CSS color overrides for the flv flow player */
1695 for(var i=0; i<M.util.video_players.length; i++) {
1696 var video = M.util.video_players[i];
1697 if (video.width > 0 && video.height > 0) {
1698 var src = {src: M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.7.swf', width: video.width, height: video.height};
1700 var src = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.7.swf';
1702 flowplayer(video.id, src, {
1703 plugins: {controls: controls},
1705 url: video.fileurl, autoPlay: false, autoBuffering: true, scaling: 'fit', mvideo: video,
1706 onMetaData: function(clip) {
1707 if (clip.mvideo.autosize && !clip.mvideo.resized) {
1708 clip.mvideo.resized = true;
1709 //alert("metadata!!! "+clip.width+' '+clip.height+' '+JSON.stringify(clip.metaData));
1710 if (typeof(clip.metaData.width) == 'undefined' || typeof(clip.metaData.height) == 'undefined') {
1711 // bad luck, we have to guess - we may not get metadata at all
1712 var width = clip.width;
1713 var height = clip.height;
1715 var width = clip.metaData.width;
1716 var height = clip.metaData.height;
1718 var minwidth = 300; // controls are messed up in smaller objects
1719 if (width < minwidth) {
1720 height = (height * minwidth) / width;
1724 var object = this._api();
1725 object.width = width;
1726 object.height = height;
1732 if (M.util.audio_players.length == 0) {
1745 backgroundGradient: [0.5,0,0.3]
1749 for (var j=0; j < document.styleSheets.length; j++) {
1751 // To avoid javascript security violation accessing cross domain stylesheets
1752 var allrules = false;
1754 if (typeof (document.styleSheets[j].rules) != 'undefined') {
1755 allrules = document.styleSheets[j].rules;
1756 } else if (typeof (document.styleSheets[j].cssRules) != 'undefined') {
1757 allrules = document.styleSheets[j].cssRules;
1766 // On cross domain style sheets Chrome V8 allows access to rules but returns null
1771 for(var i=0; i<allrules.length; i++) {
1773 if (/^\.mp3flowplayer_.*Color$/.test(allrules[i].selectorText)) {
1774 if (typeof(allrules[i].cssText) != 'undefined') {
1775 rule = allrules[i].cssText;
1776 } else if (typeof(allrules[i].style.cssText) != 'undefined') {
1777 rule = allrules[i].style.cssText;
1779 if (rule != '' && /.*color\s*:\s*([^;]+).*/gi.test(rule)) {
1780 rule = rule.replace(/.*color\s*:\s*([^;]+).*/gi, '$1');
1781 var colprop = allrules[i].selectorText.replace(/^\.mp3flowplayer_/, '');
1782 controls[colprop] = rule;
1789 for(i=0; i<M.util.audio_players.length; i++) {
1790 var audio = M.util.audio_players[i];
1792 controls.controlall = false;
1793 controls.height = 15;
1794 controls.time = false;
1796 controls.controlall = true;
1797 controls.height = 25;
1798 controls.time = true;
1800 flowplayer(audio.id, M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.7.swf', {
1801 plugins: {controls: controls, audio: {url: M.cfg.wwwroot + '/lib/flowplayer/flowplayer.audio-3.2.2.swf'}},
1802 clip: {url: audio.fileurl, provider: "audio", autoPlay: false}
1807 if (M.cfg.jsrev == -10) {
1808 var jsurl = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.6.js';
1810 var jsurl = M.cfg.wwwroot + '/lib/javascript.php?file=/lib/flowplayer/flowplayer-3.2.6.js&rev=' + M.cfg.jsrev;
1812 var fileref = document.createElement('script');
1813 fileref.setAttribute('type','text/javascript');
1814 fileref.setAttribute('src', jsurl);
1815 fileref.onload = embed_function;
1816 fileref.onreadystatechange = embed_function;
1817 document.getElementsByTagName('head')[0].appendChild(fileref);