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 loaded from YUI.
6 * @param {Array} modules
8 M.yui.add_module = function(modules) {
9 for (var modname in modules) {
10 YUI_config.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) {
37 if (!component || component == '' || component == 'moodle' || component == 'core') {
41 var url = M.cfg.wwwroot + '/theme/image.php';
42 if (M.cfg.themerev > 0 && M.cfg.slasharguments == 1) {
43 if (!M.cfg.svgicons) {
46 url += '/' + M.cfg.theme + '/' + component + '/' + M.cfg.themerev + '/' + imagename;
48 url += '?theme=' + M.cfg.theme + '&component=' + component + '&rev=' + M.cfg.themerev + '&image=' + imagename;
49 if (!M.cfg.svgicons) {
57 M.util.in_array = function(item, array){
58 for( var i = 0; i<array.length; i++){
67 * Init a collapsible region, see print_collapsible_region in weblib.php
68 * @param {YUI} Y YUI3 instance with all libraries loaded
69 * @param {String} id the HTML id for the div.
70 * @param {String} userpref the user preference that records the state of this box. false if none.
71 * @param {String} strtooltip
73 M.util.init_collapsible_region = function(Y, id, userpref, strtooltip) {
74 Y.use('anim', function(Y) {
75 new M.util.CollapsibleRegion(Y, id, userpref, strtooltip);
80 * Object to handle a collapsible region : instantiate and forget styled object
84 * @param {YUI} Y YUI3 instance with all libraries loaded
85 * @param {String} id The HTML id for the div.
86 * @param {String} userpref The user preference that records the state of this box. false if none.
87 * @param {String} strtooltip
89 M.util.CollapsibleRegion = function(Y, id, userpref, strtooltip) {
90 // Record the pref name
91 this.userpref = userpref;
93 // Find the divs in the document.
94 this.div = Y.one('#'+id);
96 // Get the caption for the collapsible region
97 var caption = this.div.one('#'+id + '_caption');
100 var a = Y.Node.create('<a href="#"></a>');
101 a.setAttribute('title', strtooltip);
103 // Get all the nodes from caption, remove them and append them to <a>
104 while (caption.hasChildNodes()) {
105 child = caption.get('firstChild');
111 // Get the height of the div at this point before we shrink it if required
112 var height = this.div.get('offsetHeight');
113 var collapsedimage = 't/collapsed'; // ltr mode
114 if (right_to_left()) {
115 collapsedimage = 't/collapsed_rtl';
117 collapsedimage = 't/collapsed';
119 if (this.div.hasClass('collapsed')) {
120 // Add the correct image and record the YUI node created in the process
121 this.icon = Y.Node.create('<img src="'+M.util.image_url(collapsedimage, 'moodle')+'" alt="" />');
122 // Shrink the div as it is collapsed by default
123 this.div.setStyle('height', caption.get('offsetHeight')+'px');
125 // Add the correct image and record the YUI node created in the process
126 this.icon = Y.Node.create('<img src="'+M.util.image_url('t/expanded', 'moodle')+'" alt="" />');
130 // Create the animation.
131 var animation = new Y.Anim({
134 easing: Y.Easing.easeBoth,
135 to: {height:caption.get('offsetHeight')},
136 from: {height:height}
139 // Handler for the animation finishing.
140 animation.on('end', function() {
141 this.div.toggleClass('collapsed');
142 var collapsedimage = 't/collapsed'; // ltr mode
143 if (right_to_left()) {
144 collapsedimage = 't/collapsed_rtl';
146 collapsedimage = 't/collapsed';
148 if (this.div.hasClass('collapsed')) {
149 this.icon.set('src', M.util.image_url(collapsedimage, 'moodle'));
151 this.icon.set('src', M.util.image_url('t/expanded', 'moodle'));
155 // Hook up the event handler.
156 a.on('click', function(e, animation) {
158 // Animate to the appropriate size.
159 if (animation.get('running')) {
162 animation.set('reverse', this.div.hasClass('collapsed'));
163 // Update the user preference.
165 M.util.set_user_preference(this.userpref, !this.div.hasClass('collapsed'));
172 * The user preference that stores the state of this box.
176 M.util.CollapsibleRegion.prototype.userpref = null;
179 * The key divs that make up this
183 M.util.CollapsibleRegion.prototype.div = null;
186 * The key divs that make up this
190 M.util.CollapsibleRegion.prototype.icon = null;
193 * Makes a best effort to connect back to Moodle to update a user preference,
194 * however, there is no mechanism for finding out if the update succeeded.
196 * Before you can use this function in your JavsScript, you must have called
197 * user_preference_allow_ajax_update from moodlelib.php to tell Moodle that
198 * the udpate is allowed, and how to safely clean and submitted values.
200 * @param String name the name of the setting to udpate.
201 * @param String the value to set it to.
203 M.util.set_user_preference = function(name, value) {
204 YUI().use('io', function(Y) {
205 var url = M.cfg.wwwroot + '/lib/ajax/setuserpref.php?sesskey=' +
206 M.cfg.sesskey + '&pref=' + encodeURI(name) + '&value=' + encodeURI(value);
208 // If we are a developer, ensure that failures are reported.
213 if (M.cfg.developerdebug) {
214 cfg.on.failure = function(id, o, args) {
215 alert("Error updating user preference '" + name + "' using ajax. Clicking this link will repeat the Ajax call that failed so you can see the error: ");
225 * Prints a confirmation dialog in the style of DOM.confirm().
226 * @param object event A YUI DOM event or null if launched manually
227 * @param string message The message to show in the dialog
228 * @param string url The URL to forward to if YES is clicked. Disabled if fn is given
229 * @param function fn A JS function to run if YES is clicked.
231 M.util.show_confirm_dialog = function(e, args) {
232 var target = e.target;
233 if (e.preventDefault) {
237 YUI().use('yui2-container', 'yui2-event', function(Y) {
238 var simpledialog = new Y.YUI2.widget.SimpleDialog('confirmdialog',
247 simpledialog.setHeader(M.str.admin.confirmation);
248 simpledialog.setBody(args.message);
249 simpledialog.cfg.setProperty('icon', Y.YUI2.widget.SimpleDialog.ICON_WARN);
251 var handle_cancel = function() {
255 var handle_yes = function() {
259 // args comes from PHP, so callback will be a string, needs to be evaluated by JS
261 if (Y.Lang.isFunction(args.callback)) {
262 callback = args.callback;
264 callback = eval('('+args.callback+')');
267 if (Y.Lang.isObject(args.scope)) {
273 if (args.callbackargs) {
274 callback.apply(sc, args.callbackargs);
281 var targetancestor = null,
284 if (target.test('a')) {
285 window.location = target.get('href');
287 } else if ((targetancestor = target.ancestor('a')) !== null) {
288 window.location = targetancestor.get('href');
290 } else if (target.test('input')) {
291 targetform = target.ancestor(function(node) { return node.get('tagName').toLowerCase() == 'form'; });
292 // We cannot use target.ancestor('form') on the previous line
293 // because of http://yuilibrary.com/projects/yui3/ticket/2531561
297 if (target.get('name') && target.get('value')) {
298 targetform.append('<input type="hidden" name="' + target.get('name') +
299 '" value="' + target.get('value') + '">');
303 } else if (target.get('tagName').toLowerCase() == 'form') {
304 // We cannot use target.test('form') on the previous line because of
305 // http://yuilibrary.com/projects/yui3/ticket/2531561
308 } else if (M.cfg.developerdebug) {
309 alert("Element of type " + target.get('tagName') + " is not supported by the M.util.show_confirm_dialog function. Use A, INPUT, or FORM");
313 if (!args.cancellabel) {
314 args.cancellabel = M.str.moodle.cancel;
316 if (!args.continuelabel) {
317 args.continuelabel = M.str.moodle.yes;
321 {text: args.cancellabel, handler: handle_cancel, isDefault: true},
322 {text: args.continuelabel, handler: handle_yes}
325 simpledialog.cfg.queueProperty('buttons', buttons);
327 simpledialog.render(document.body);
332 /** Useful for full embedding of various stuff */
333 M.util.init_maximised_embed = function(Y, id) {
334 var obj = Y.one('#'+id);
339 var get_htmlelement_size = function(el, prop) {
340 if (Y.Lang.isString(el)) {
341 el = Y.one('#' + el);
343 // Ensure element exists.
345 var val = el.getStyle(prop);
347 val = el.getComputedStyle(prop);
349 return parseInt(val);
355 var resize_object = function() {
356 obj.setStyle('width', '0px');
357 obj.setStyle('height', '0px');
358 var newwidth = get_htmlelement_size('maincontent', 'width') - 35;
360 if (newwidth > 500) {
361 obj.setStyle('width', newwidth + 'px');
363 obj.setStyle('width', '500px');
366 var headerheight = get_htmlelement_size('page-header', 'height');
367 var footerheight = get_htmlelement_size('page-footer', 'height');
368 var newheight = parseInt(Y.one('body').get('winHeight')) - footerheight - headerheight - 100;
369 if (newheight < 400) {
372 obj.setStyle('height', newheight+'px');
376 // fix layout if window resized too
377 window.onresize = function() {
383 * Attach handler to single_select
385 * This code was deprecated in Moodle 2.4 and will be removed in Moodle 2.6
387 * Please see lib/yui/formautosubmit/formautosubmit.js for its replacement
389 M.util.init_select_autosubmit = function(Y, formid, selectid, nothing) {
390 if (M.cfg.developerdebug) {
391 Y.log("You are using a deprecated function call (M.util.init_select_autosubmit). Please look at rewriting your call to use moodle-core-formautosubmit");
393 Y.use('event-key', function() {
394 var select = Y.one('#'+selectid);
396 // Try to get the form by id
397 var form = Y.one('#'+formid) || (function(){
398 // Hmmm the form's id may have been overriden by an internal input
399 // with the name id which will KILL IE.
400 // We need to manually iterate at this point because if the case
401 // above is true YUI's ancestor method will also kill IE!
403 while (form && form.get('nodeName').toUpperCase() !== 'FORM') {
404 form = form.ancestor();
408 // Make sure we have the form
411 // Create a function to handle our change event
412 var processchange = function(e, paramobject) {
413 if ((nothing===false || select.get('value') != nothing) && paramobject.lastindex != select.get('selectedIndex')) {
414 // chrome doesn't pick up on a click when selecting an element in a select menu, so we use
415 // the on change event to fire this function. This just checks to see if a button was
416 // first pressed before redirecting to the appropriate page.
417 if (Y.UA.os == 'windows' && Y.UA.chrome){
418 if (buttonflag == 1) {
429 paramobject.lastindex = select.get('selectedIndex');
432 var changedown = function(e, paramobject) {
433 if ((nothing===false || select.get('value') != nothing) && paramobject.lastindex != select.get('selectedIndex')) {
434 if(e.keyCode == 13) {
437 paramobject.lastindex = select.get('selectedIndex');
441 var paramobject = new Object();
442 paramobject.lastindex = select.get('selectedIndex');
443 paramobject.eventchangeorblur = select.on('click', processchange, form, paramobject);
444 // Bad hack to circumvent problems with different browsers on different systems.
445 if (Y.UA.os == 'macintosh') {
447 paramobject.eventchangeorblur = select.on('change', processchange, form, paramobject);
449 paramobject.eventkeypress = Y.on('key', processchange, select, 'press:13', form, paramobject);
451 if(Y.UA.os == 'windows' && Y.UA.chrome) {
452 paramobject.eventchangeorblur = select.on('change', processchange, form, paramobject);
454 paramobject.eventkeypress = Y.on('keydown', changedown, select, '', form, paramobject);
462 * Attach handler to url_select
463 * Deprecated from 2.4 onwards.
464 * Please use @see init_select_autosubmit() for redirecting to a url (above).
465 * This function has accessability issues and also does not use the formid passed through as a parameter.
467 M.util.init_url_select = function(Y, formid, selectid, nothing) {
468 if (M.cfg.developerdebug) {
469 Y.log("You are using a deprecated function call (M.util.init_url_select). Please look at rewriting your call to use moodle-core-formautosubmit");
471 YUI().use('node', function(Y) {
472 Y.on('change', function() {
473 if ((nothing == false && Y.Lang.isBoolean(nothing)) || Y.one('#'+selectid).get('value') != nothing) {
474 window.location = M.cfg.wwwroot+Y.one('#'+selectid).get('value');
482 * Breaks out all links to the top frame - used in frametop page layout.
484 M.util.init_frametop = function(Y) {
485 Y.all('a').each(function(node) {
486 node.set('target', '_top');
488 Y.all('form').each(function(node) {
489 node.set('target', '_top');
494 * Finds all nodes that match the given CSS selector and attaches events to them
495 * so that they toggle a given classname when clicked.
498 * @param {string} id An id containing elements to target
499 * @param {string} cssselector A selector to use to find targets
500 * @param {string} toggleclassname A classname to toggle
502 M.util.init_toggle_class_on_click = function(Y, id, cssselector, toggleclassname, togglecssselector) {
504 if (togglecssselector == '') {
505 togglecssselector = cssselector;
508 var node = Y.one('#'+id);
509 node.all(cssselector).each(function(n){
510 n.on('click', function(e){
512 if (e.target.test(cssselector) && !e.target.test('a') && !e.target.test('img')) {
513 if (this.test(togglecssselector)) {
514 this.toggleClass(toggleclassname);
516 this.ancestor(togglecssselector).toggleClass(toggleclassname);
521 // Attach this click event to the node rather than all selectors... will be much better
523 node.on('click', function(e){
524 if (e.target.hasClass('addtoall')) {
525 this.all(togglecssselector).addClass(toggleclassname);
526 } else if (e.target.hasClass('removefromall')) {
527 this.all(togglecssselector+'.'+toggleclassname).removeClass(toggleclassname);
533 * Initialises a colour picker
535 * Designed to be used with admin_setting_configcolourpicker although could be used
536 * anywhere, just give a text input an id and insert a div with the class admin_colourpicker
537 * above or below the input (must have the same parent) and then call this with the
540 * This code was mostly taken from my [Sam Hemelryk] css theme tool available in
541 * contrib/blocks. For better docs refer to that.
545 * @param {object} previewconf
547 M.util.init_colour_picker = function(Y, id, previewconf) {
549 * We need node and event-mouseenter
551 Y.use('node', 'event-mouseenter', function(){
553 * The colour picker object
562 eventMouseEnter : null,
563 eventMouseLeave : null,
564 eventMouseMove : null,
569 * Initalises the colour picker by putting everything together and wiring the events
572 this.input = Y.one('#'+id);
573 this.box = this.input.ancestor().one('.admin_colourpicker');
574 this.image = Y.Node.create('<img alt="" class="colourdialogue" />');
575 this.image.setAttribute('src', M.util.image_url('i/colourpicker', 'moodle'));
576 this.preview = Y.Node.create('<div class="previewcolour"></div>');
577 this.preview.setStyle('width', this.height/2).setStyle('height', this.height/2).setStyle('backgroundColor', this.input.get('value'));
578 this.current = Y.Node.create('<div class="currentcolour"></div>');
579 this.current.setStyle('width', this.height/2).setStyle('height', this.height/2 -1).setStyle('backgroundColor', this.input.get('value'));
580 this.box.setContent('').append(this.image).append(this.preview).append(this.current);
582 if (typeof(previewconf) === 'object' && previewconf !== null) {
583 Y.one('#'+id+'_preview').on('click', function(e){
584 if (Y.Lang.isString(previewconf.selector)) {
585 Y.all(previewconf.selector).setStyle(previewconf.style, this.input.get('value'));
587 for (var i in previewconf.selector) {
588 Y.all(previewconf.selector[i]).setStyle(previewconf.style, this.input.get('value'));
594 this.eventClick = this.image.on('click', this.pickColour, this);
595 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
598 * Starts to follow the mouse once it enter the image
600 startFollow : function(e) {
601 this.eventMouseEnter.detach();
602 this.eventMouseLeave = Y.on('mouseleave', this.endFollow, this.image, this);
603 this.eventMouseMove = this.image.on('mousemove', function(e){
604 this.preview.setStyle('backgroundColor', this.determineColour(e));
608 * Stops following the mouse
610 endFollow : function(e) {
611 this.eventMouseMove.detach();
612 this.eventMouseLeave.detach();
613 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
616 * Picks the colour the was clicked on
618 pickColour : function(e) {
619 var colour = this.determineColour(e);
620 this.input.set('value', colour);
621 this.current.setStyle('backgroundColor', colour);
624 * Calculates the colour fromthe given co-ordinates
626 determineColour : function(e) {
627 var eventx = Math.floor(e.pageX-e.target.getX());
628 var eventy = Math.floor(e.pageY-e.target.getY());
630 var imagewidth = this.width;
631 var imageheight = this.height;
632 var factor = this.factor;
633 var colour = [255,0,0];
644 var matrixcount = matrices.length;
645 var limit = Math.round(imagewidth/matrixcount);
646 var heightbreak = Math.round(imageheight/2);
648 for (var x = 0; x < imagewidth; x++) {
649 var divisor = Math.floor(x / limit);
650 var matrix = matrices[divisor];
652 colour[0] += matrix[0]*factor;
653 colour[1] += matrix[1]*factor;
654 colour[2] += matrix[2]*factor;
661 var pixel = [colour[0], colour[1], colour[2]];
662 if (eventy < heightbreak) {
663 pixel[0] += Math.floor(((255-pixel[0])/heightbreak) * (heightbreak - eventy));
664 pixel[1] += Math.floor(((255-pixel[1])/heightbreak) * (heightbreak - eventy));
665 pixel[2] += Math.floor(((255-pixel[2])/heightbreak) * (heightbreak - eventy));
666 } else if (eventy > heightbreak) {
667 pixel[0] = Math.floor((imageheight-eventy)*(pixel[0]/heightbreak));
668 pixel[1] = Math.floor((imageheight-eventy)*(pixel[1]/heightbreak));
669 pixel[2] = Math.floor((imageheight-eventy)*(pixel[2]/heightbreak));
672 return this.convert_rgb_to_hex(pixel);
675 * Converts an RGB value to Hex
677 convert_rgb_to_hex : function(rgb) {
679 var hexchars = "0123456789ABCDEF";
680 for (var i=0; i<3; i++) {
681 var number = Math.abs(rgb[i]);
682 if (number == 0 || isNaN(number)) {
685 hex += hexchars.charAt((number-number%16)/16)+hexchars.charAt(number%16);
692 * Initialise the colour picker :) Hoorah
698 M.util.init_block_hider = function(Y, config) {
699 Y.use('base', 'node', function(Y) {
700 M.util.block_hider = M.util.block_hider || (function(){
701 var blockhider = function() {
702 blockhider.superclass.constructor.apply(this, arguments);
704 blockhider.prototype = {
705 initializer : function(config) {
706 this.set('block', '#'+this.get('id'));
707 var b = this.get('block'),
710 if (t && (a = t.one('.block_action'))) {
711 var hide = Y.Node.create('<img class="block-hider-hide" tabindex="0" alt="'+config.tooltipVisible+'" title="'+config.tooltipVisible+'" />');
712 hide.setAttribute('src', this.get('iconVisible')).on('click', this.updateState, this, true);
713 hide.on('keypress', this.updateStateKey, this, true);
714 var show = Y.Node.create('<img class="block-hider-show" tabindex="0" alt="'+config.tooltipHidden+'" title="'+config.tooltipHidden+'" />');
715 show.setAttribute('src', this.get('iconHidden')).on('click', this.updateState, this, false);
716 show.on('keypress', this.updateStateKey, this, false);
717 a.insert(show, 0).insert(hide, 0);
720 updateState : function(e, hide) {
721 M.util.set_user_preference(this.get('preference'), hide);
723 this.get('block').addClass('hidden');
725 this.get('block').removeClass('hidden');
728 updateStateKey : function(e, hide) {
729 if (e.keyCode == 13) { //allow hide/show via enter key
730 this.updateState(this, hide);
734 Y.extend(blockhider, Y.Base, blockhider.prototype, {
740 value : M.util.image_url('t/switch_minus', 'moodle')
743 value : M.util.image_url('t/switch_plus', 'moodle')
746 setter : function(node) {
754 new M.util.block_hider(config);
759 * Returns a string registered in advance for usage in JavaScript
761 * If you do not pass the third parameter, the function will just return
762 * the corresponding value from the M.str object. If the third parameter is
763 * provided, the function performs {$a} placeholder substitution in the
764 * same way as PHP get_string() in Moodle does.
766 * @param {String} identifier string identifier
767 * @param {String} component the component providing the string
768 * @param {Object|String} a optional variable to populate placeholder with
770 M.util.get_string = function(identifier, component, a) {
773 if (M.cfg.developerdebug) {
774 // creating new instance if YUI is not optimal but it seems to be better way then
775 // require the instance via the function API - note that it is used in rare cases
776 // for debugging only anyway
777 // To ensure we don't kill browser performance if hundreds of get_string requests
778 // are made we cache the instance we generate within the M.util namespace.
779 // We don't publicly define the variable so that it doesn't get abused.
780 if (typeof M.util.get_string_yui_instance === 'undefined') {
781 M.util.get_string_yui_instance = new YUI({ debug : true });
783 var Y = M.util.get_string_yui_instance;
786 if (!M.str.hasOwnProperty(component) || !M.str[component].hasOwnProperty(identifier)) {
787 stringvalue = '[[' + identifier + ',' + component + ']]';
788 if (M.cfg.developerdebug) {
789 Y.log('undefined string ' + stringvalue, 'warn', 'M.util.get_string');
794 stringvalue = M.str[component][identifier];
796 if (typeof a == 'undefined') {
797 // no placeholder substitution requested
801 if (typeof a == 'number' || typeof a == 'string') {
802 // replace all occurrences of {$a} with the placeholder value
803 stringvalue = stringvalue.replace(/\{\$a\}/g, a);
807 if (typeof a == 'object') {
808 // replace {$a->key} placeholders
810 if (typeof a[key] != 'number' && typeof a[key] != 'string') {
811 if (M.cfg.developerdebug) {
812 Y.log('invalid value type for $a->' + key, 'warn', 'M.util.get_string');
816 var search = '{$a->' + key + '}';
817 search = search.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
818 search = new RegExp(search, 'g');
819 stringvalue = stringvalue.replace(search, a[key]);
824 if (M.cfg.developerdebug) {
825 Y.log('incorrect placeholder type', 'warn', 'M.util.get_string');
831 * Set focus on username or password field of the login form
833 M.util.focus_login_form = function(Y) {
834 var username = Y.one('#username');
835 var password = Y.one('#password');
837 if (username == null || password == null) {
838 // something is wrong here
842 var curElement = document.activeElement
843 if (curElement == 'undefined') {
844 // legacy browser - skip refocus protection
845 } else if (curElement.tagName == 'INPUT') {
846 // user was probably faster to focus something, do not mess with focus
850 if (username.get('value') == '') {
858 * Adds lightbox hidden element that covers the whole node.
861 * @param {Node} the node lightbox should be added to
862 * @retun {Node} created lightbox node
864 M.util.add_lightbox = function(Y, node) {
865 var WAITICON = {'pix':"i/loading_small",'component':'moodle'};
867 // Check if lightbox is already there
868 if (node.one('.lightbox')) {
869 return node.one('.lightbox');
872 node.setStyle('position', 'relative');
873 var waiticon = Y.Node.create('<img />')
875 'src' : M.util.image_url(WAITICON.pix, WAITICON.component)
878 'position' : 'relative',
882 var lightbox = Y.Node.create('<div></div>')
885 'position' : 'absolute',
890 'backgroundColor' : 'white',
891 'textAlign' : 'center'
893 .setAttribute('class', 'lightbox')
896 lightbox.appendChild(waiticon);
897 node.append(lightbox);
902 * Appends a hidden spinner element to the specified node.
905 * @param {Node} the node the spinner should be added to
906 * @return {Node} created spinner node
908 M.util.add_spinner = function(Y, node) {
909 var WAITICON = {'pix':"i/loading_small",'component':'moodle'};
911 // Check if spinner is already there
912 if (node.one('.spinner')) {
913 return node.one('.spinner');
916 var spinner = Y.Node.create('<img />')
917 .setAttribute('src', M.util.image_url(WAITICON.pix, WAITICON.component))
919 .addClass('iconsmall')
922 node.append(spinner);
926 //=== old legacy JS code, hopefully to be replaced soon by M.xx.yy and YUI3 code ===
928 function checkall() {
929 var inputs = document.getElementsByTagName('input');
930 for (var i = 0; i < inputs.length; i++) {
931 if (inputs[i].type == 'checkbox') {
932 if (inputs[i].disabled || inputs[i].readOnly) {
935 inputs[i].checked = true;
940 function checknone() {
941 var inputs = document.getElementsByTagName('input');
942 for (var i = 0; i < inputs.length; i++) {
943 if (inputs[i].type == 'checkbox') {
944 if (inputs[i].disabled || inputs[i].readOnly) {
947 inputs[i].checked = false;
953 * Either check, or uncheck, all checkboxes inside the element with id is
954 * @param id the id of the container
955 * @param checked the new state, either '' or 'checked'.
957 function select_all_in_element_with_id(id, checked) {
958 var container = document.getElementById(id);
962 var inputs = container.getElementsByTagName('input');
963 for (var i = 0; i < inputs.length; ++i) {
964 if (inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
965 inputs[i].checked = checked;
970 function select_all_in(elTagName, elClass, elId) {
971 var inputs = document.getElementsByTagName('input');
972 inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
973 for(var i = 0; i < inputs.length; ++i) {
974 if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
975 inputs[i].checked = 'checked';
980 function deselect_all_in(elTagName, elClass, elId) {
981 var inputs = document.getElementsByTagName('INPUT');
982 inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
983 for(var i = 0; i < inputs.length; ++i) {
984 if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
985 inputs[i].checked = '';
990 function confirm_if(expr, message) {
994 return confirm(message);
999 findParentNode (start, elementName, elementClass, elementID)
1001 Travels up the DOM hierarchy to find a parent element with the
1002 specified tag name, class, and id. All conditions must be met,
1003 but any can be ommitted. Returns the BODY element if no match
1006 function findParentNode(el, elName, elClass, elId) {
1007 while (el.nodeName.toUpperCase() != 'BODY') {
1008 if ((!elName || el.nodeName.toUpperCase() == elName) &&
1009 (!elClass || el.className.indexOf(elClass) != -1) &&
1010 (!elId || el.id == elId)) {
1018 findChildNode (start, elementName, elementClass, elementID)
1020 Travels down the DOM hierarchy to find all child elements with the
1021 specified tag name, class, and id. All conditions must be met,
1022 but any can be ommitted.
1023 Doesn't examine children of matches.
1025 function findChildNodes(start, tagName, elementClass, elementID, elementName) {
1026 var children = new Array();
1027 for (var i = 0; i < start.childNodes.length; i++) {
1028 var classfound = false;
1029 var child = start.childNodes[i];
1030 if((child.nodeType == 1) &&//element node type
1031 (elementClass && (typeof(child.className)=='string'))) {
1032 var childClasses = child.className.split(/\s+/);
1033 for (var childClassIndex in childClasses) {
1034 if (childClasses[childClassIndex]==elementClass) {
1040 if(child.nodeType == 1) { //element node type
1041 if ( (!tagName || child.nodeName == tagName) &&
1042 (!elementClass || classfound)&&
1043 (!elementID || child.id == elementID) &&
1044 (!elementName || child.name == elementName))
1046 children = children.concat(child);
1048 children = children.concat(findChildNodes(child, tagName, elementClass, elementID, elementName));
1055 function unmaskPassword(id) {
1056 var pw = document.getElementById(id);
1057 var chb = document.getElementById(id+'unmask');
1060 // first try IE way - it can not set name attribute later
1062 var newpw = document.createElement('<input type="text" autocomplete="off" name="'+pw.name+'">');
1064 var newpw = document.createElement('<input type="password" autocomplete="off" name="'+pw.name+'">');
1066 newpw.attributes['class'].nodeValue = pw.attributes['class'].nodeValue;
1068 var newpw = document.createElement('input');
1069 newpw.setAttribute('autocomplete', 'off');
1070 newpw.setAttribute('name', pw.name);
1072 newpw.setAttribute('type', 'text');
1074 newpw.setAttribute('type', 'password');
1076 newpw.setAttribute('class', pw.getAttribute('class'));
1079 newpw.size = pw.size;
1080 newpw.onblur = pw.onblur;
1081 newpw.onchange = pw.onchange;
1082 newpw.value = pw.value;
1083 pw.parentNode.replaceChild(newpw, pw);
1086 function filterByParent(elCollection, parentFinder) {
1087 var filteredCollection = [];
1088 for (var i = 0; i < elCollection.length; ++i) {
1089 var findParent = parentFinder(elCollection[i]);
1090 if (findParent.nodeName.toUpperCase() != 'BODY') {
1091 filteredCollection.push(elCollection[i]);
1094 return filteredCollection;
1098 All this is here just so that IE gets to handle oversized blocks
1099 in a visually pleasing manner. It does a browser detect. So sue me.
1102 function fix_column_widths() {
1103 var agt = navigator.userAgent.toLowerCase();
1104 if ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1)) {
1105 fix_column_width('left-column');
1106 fix_column_width('right-column');
1110 function fix_column_width(colName) {
1111 if(column = document.getElementById(colName)) {
1112 if(!column.offsetWidth) {
1113 setTimeout("fix_column_width('" + colName + "')", 20);
1118 var nodes = column.childNodes;
1120 for(i = 0; i < nodes.length; ++i) {
1121 if(nodes[i].className.indexOf("block") != -1 ) {
1122 if(width < nodes[i].offsetWidth) {
1123 width = nodes[i].offsetWidth;
1128 for(i = 0; i < nodes.length; ++i) {
1129 if(nodes[i].className.indexOf("block") != -1 ) {
1130 nodes[i].style.width = width + 'px';
1138 Insert myValue at current cursor position
1140 function insertAtCursor(myField, myValue) {
1142 if (document.selection) {
1144 sel = document.selection.createRange();
1147 // Mozilla/Netscape support
1148 else if (myField.selectionStart || myField.selectionStart == '0') {
1149 var startPos = myField.selectionStart;
1150 var endPos = myField.selectionEnd;
1151 myField.value = myField.value.substring(0, startPos)
1152 + myValue + myField.value.substring(endPos, myField.value.length);
1154 myField.value += myValue;
1160 Call instead of setting window.onload directly or setting body onload=.
1161 Adds your function to a chain of functions rather than overwriting anything
1164 function addonload(fn) {
1165 var oldhandler=window.onload;
1166 window.onload=function() {
1167 if(oldhandler) oldhandler();
1172 * Replacement for getElementsByClassName in browsers that aren't cool enough
1174 * Relying on the built-in getElementsByClassName is far, far faster than
1177 * Note: the third argument used to be an object with odd behaviour. It now
1178 * acts like the 'name' in the HTML5 spec, though the old behaviour is still
1179 * mimicked if you pass an object.
1181 * @param {Node} oElm The top-level node for searching. To search a whole
1182 * document, use `document`.
1183 * @param {String} strTagName filter by tag names
1184 * @param {String} name same as HTML5 spec
1186 function getElementsByClassName(oElm, strTagName, name) {
1187 // for backwards compatibility
1188 if(typeof name == "object") {
1189 var names = new Array();
1190 for(var i=0; i<name.length; i++) names.push(names[i]);
1191 name = names.join('');
1193 // use native implementation if possible
1194 if (oElm.getElementsByClassName && Array.filter) {
1195 if (strTagName == '*') {
1196 return oElm.getElementsByClassName(name);
1198 return Array.filter(oElm.getElementsByClassName(name), function(el) {
1199 return el.nodeName.toLowerCase() == strTagName.toLowerCase();
1203 // native implementation unavailable, fall back to slow method
1204 var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
1205 var arrReturnElements = new Array();
1206 var arrRegExpClassNames = new Array();
1207 var names = name.split(' ');
1208 for(var i=0; i<names.length; i++) {
1209 arrRegExpClassNames.push(new RegExp("(^|\\s)" + names[i].replace(/\-/g, "\\-") + "(\\s|$)"));
1213 for(var j=0; j<arrElements.length; j++) {
1214 oElement = arrElements[j];
1216 for(var k=0; k<arrRegExpClassNames.length; k++) {
1217 if(!arrRegExpClassNames[k].test(oElement.className)) {
1218 bMatchesAll = false;
1223 arrReturnElements.push(oElement);
1226 return (arrReturnElements)
1230 * Return whether we are in right to left mode or not.
1234 function right_to_left() {
1235 var body = Y.one('body');
1237 if (body && body.hasClass('dir-rtl')) {
1243 function openpopup(event, args) {
1246 if (event.preventDefault) {
1247 event.preventDefault();
1249 event.returnValue = false;
1253 // Make sure the name argument is set and valid.
1254 var nameregex = /[^a-z0-9_]/i;
1255 if (typeof args.name !== 'string') {
1256 args.name = '_blank';
1257 } else if (args.name.match(nameregex)) {
1258 // Cleans window name because IE does not support funky ones.
1259 args.name = args.name.replace(nameregex, '_');
1260 if (M.cfg.developerdebug) {
1261 alert('DEVELOPER NOTICE: Invalid \'name\' passed to openpopup()');
1265 var fullurl = args.url;
1266 if (!args.url.match(/https?:\/\//)) {
1267 fullurl = M.cfg.wwwroot + args.url;
1269 if (args.fullscreen) {
1270 args.options = args.options.
1271 replace(/top=\d+/, 'top=0').
1272 replace(/left=\d+/, 'left=0').
1273 replace(/width=\d+/, 'width=' + screen.availWidth).
1274 replace(/height=\d+/, 'height=' + screen.availHeight);
1276 var windowobj = window.open(fullurl,args.name,args.options);
1281 if (args.fullscreen) {
1282 // In some browser / OS combinations (E.g. Chrome on Windows), the
1283 // window initially opens slighly too big. The width and heigh options
1284 // seem to control the area inside the browser window, so what with
1285 // scroll-bars, etc. the actual window is bigger than the screen.
1286 // Therefore, we need to fix things up after the window is open.
1287 var hackcount = 100;
1288 var get_size_exactly_right = function() {
1289 windowobj.moveTo(0, 0);
1290 windowobj.resizeTo(screen.availWidth, screen.availHeight);
1292 // Unfortunately, it seems that in Chrome on Ubuntu, if you call
1293 // something like windowobj.resizeTo(1280, 1024) too soon (up to
1294 // about 50ms) after the window is open, then it actually behaves
1295 // as if you called windowobj.resizeTo(0, 0). Therefore, we need to
1296 // check that the resize actually worked, and if not, repeatedly try
1297 // again after a short delay until it works (but with a limit of
1298 // hackcount repeats.
1299 if (hackcount > 0 && (windowobj.innerHeight < 10 || windowobj.innerWidth < 10)) {
1301 setTimeout(get_size_exactly_right, 10);
1304 setTimeout(get_size_exactly_right, 0);
1311 /** Close the current browser window. */
1312 function close_window(e) {
1313 if (e.preventDefault) {
1316 e.returnValue = false;
1322 * Used in a couple of modules to hide navigation areas when using AJAX
1325 function show_item(itemid) {
1326 var item = document.getElementById(itemid);
1328 item.style.display = "";
1332 function destroy_item(itemid) {
1333 var item = document.getElementById(itemid);
1335 item.parentNode.removeChild(item);
1339 * Tranfer keyboard focus to the HTML element with the given id, if it exists.
1340 * @param controlid the control id.
1342 function focuscontrol(controlid) {
1343 var control = document.getElementById(controlid);
1350 * Transfers keyboard focus to an HTML element based on the old style style of focus
1351 * This function should be removed as soon as it is no longer used
1353 function old_onload_focus(formid, controlname) {
1354 if (document.forms[formid] && document.forms[formid].elements && document.forms[formid].elements[controlname]) {
1355 document.forms[formid].elements[controlname].focus();
1359 function build_querystring(obj) {
1360 return convert_object_to_string(obj, '&');
1363 function build_windowoptionsstring(obj) {
1364 return convert_object_to_string(obj, ',');
1367 function convert_object_to_string(obj, separator) {
1368 if (typeof obj !== 'object') {
1373 k = encodeURIComponent(k);
1375 if(obj[k] instanceof Array) {
1376 for(var i in value) {
1377 list.push(k+'[]='+encodeURIComponent(value[i]));
1380 list.push(k+'='+encodeURIComponent(value));
1383 return list.join(separator);
1386 function stripHTML(str) {
1387 var re = /<\S[^><]*>/g;
1388 var ret = str.replace(re, "");
1392 Number.prototype.fixed=function(n){
1394 return round(Number(this)*pow(10,n))/pow(10,n);
1396 function update_progress_bar (id, width, pt, msg, es){
1398 var status = document.getElementById("status_"+id);
1399 var percent_indicator = document.getElementById("pt_"+id);
1400 var progress_bar = document.getElementById("progress_"+id);
1401 var time_es = document.getElementById("time_"+id);
1402 status.innerHTML = msg;
1403 percent_indicator.innerHTML = percent.fixed(2) + '%';
1404 if(percent == 100) {
1405 progress_bar.style.background = "green";
1406 time_es.style.display = "none";
1408 progress_bar.style.background = "#FFCC66";
1410 time_es.innerHTML = "";
1412 time_es.innerHTML = es.fixed(2)+" sec";
1413 time_es.style.display
1417 progress_bar.style.width = width + "px";
1422 // ===== Deprecated core Javascript functions for Moodle ====
1423 // DO NOT USE!!!!!!!
1424 // Do not put this stuff in separate file because it only adds extra load on servers!
1427 * Used in a couple of modules to hide navigation areas when using AJAX
1429 function hide_item(itemid) {
1430 // use class='hiddenifjs' instead
1431 var item = document.getElementById(itemid);
1433 item.style.display = "none";
1437 M.util.help_popups = {
1438 setup : function(Y) {
1439 Y.one('body').delegate('click', this.open_popup, 'a.helplinkpopup', this);
1441 open_popup : function(e) {
1442 // Prevent the default page action
1445 // Grab the anchor that was clicked
1446 var anchor = e.target.ancestor('a', true);
1449 'url' : anchor.getAttribute('href'),
1467 args.options = options.join(',');
1473 M.util.help_icon = {
1476 initialised : false,
1477 setup : function(Y) {
1478 if (this.initialised) {
1479 // Exit early if we have already completed setup
1483 Y.one('body').delegate('click', this.display, 'span.helplink a.tooltip', this);
1484 this.initialised = true;
1486 add : function(Y, properties) {
1489 display : function(event) {
1490 event.preventDefault();
1491 if (M.util.help_icon.instance === null) {
1492 var Y = M.util.help_icon.Y;
1493 Y.use('overlay', 'io-base', 'event-mouseenter', 'node', 'event-key', 'escape', function(Y) {
1494 var help_content_overlay = {
1499 var strclose = Y.Escape.html(M.str.form.close);
1500 var footerbtn = Y.Node.create('<button class="closebtn">'+strclose+'</button>');
1501 // Create an overlay from markup
1502 this.overlay = new Y.Overlay({
1503 footerContent: footerbtn,
1510 this.overlay.render(Y.one(document.body));
1512 footerbtn.on('click', this.overlay.hide, this.overlay);
1514 var boundingBox = this.overlay.get("boundingBox");
1516 // Hide the menu if the user clicks outside of its content
1517 boundingBox.get("ownerDocument").on("mousedown", function (event) {
1518 var oTarget = event.target;
1519 var menuButton = this.helplink;
1521 if (!oTarget.compareTo(menuButton) &&
1522 !menuButton.contains(oTarget) &&
1523 !oTarget.compareTo(boundingBox) &&
1524 !boundingBox.contains(oTarget)) {
1525 this.overlay.hide();
1530 close : function(e) {
1532 this.helplink.focus();
1533 this.overlay.hide();
1536 display : function(event) {
1537 var overlayPosition;
1538 this.helplink = event.target.ancestor('span.helplink a', true);
1539 if (Y.one('html').get('dir') === 'rtl') {
1540 overlayPosition = [Y.WidgetPositionAlign.TR, Y.WidgetPositionAlign.LC];
1542 overlayPosition = [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.RC];
1545 this.overlay.set('bodyContent', Y.Node.create('<img src="'+M.cfg.loadingicon+'" class="spinner" />'));
1546 this.overlay.set("align", {node:this.helplink, points: overlayPosition});
1555 success: function(id, o, node) {
1556 this.display_callback(o.responseText);
1558 failure: function(id, o, node) {
1559 var debuginfo = o.statusText;
1560 if (M.cfg.developerdebug) {
1561 o.statusText += ' (' + ajaxurl + ')';
1563 this.display_callback('bodyContent',debuginfo);
1568 Y.io(this.helplink.get('href'), cfg);
1569 this.overlay.show();
1572 display_callback : function(content) {
1573 content = '<div role="alert">' + content + '</div>';
1574 this.overlay.set('bodyContent', content);
1577 hideContent : function() {
1579 help.overlay.hide();
1582 help_content_overlay.init();
1583 M.util.help_icon.instance = help_content_overlay;
1584 M.util.help_icon.instance.display(event);
1587 M.util.help_icon.instance.display(event);
1590 init : function(Y) {
1596 * Custom menu namespace
1598 M.core_custom_menu = {
1600 * This method is used to initialise a custom menu given the id that belongs
1601 * to the custom menu's root node.
1604 * @param {string} nodeid
1606 init : function(Y, nodeid) {
1607 var node = Y.one('#'+nodeid);
1609 Y.use('node-menunav', function(Y) {
1611 // Remove the javascript-disabled class.... obviously javascript is enabled.
1612 node.removeClass('javascript-disabled');
1613 // Initialise the menunav plugin
1614 node.plug(Y.Plugin.NodeMenuNav);
1621 * Used to store form manipulation methods and enhancments
1623 M.form = M.form || {};
1626 * Converts a nbsp indented select box into a multi drop down custom control much
1627 * like the custom menu. It also selectable categories on or off.
1629 * $form->init_javascript_enhancement('elementname','smartselect', array('selectablecategories'=>true|false, 'mode'=>'compact'|'spanning'));
1632 * @param {string} id
1633 * @param {Array} options
1635 M.form.init_smartselect = function(Y, id, options) {
1636 if (!id.match(/^id_/)) {
1639 var select = Y.one('select#'+id);
1643 Y.use('event-delegate',function(){
1649 currentvalue : null,
1653 selectablecategories : true,
1661 init : function(Y, id, args, nodes) {
1662 if (typeof(args)=='object') {
1663 for (var i in this.cfg) {
1664 if (args[i] || args[i]===false) {
1665 this.cfg[i] = args[i];
1670 // Display a loading message first up
1671 this.nodes.select = nodes.select;
1673 this.currentvalue = this.nodes.select.get('selectedIndex');
1674 this.currenttext = this.nodes.select.all('option').item(this.currentvalue).get('innerHTML');
1676 var options = Array();
1677 options[''] = {text:this.currenttext,value:'',depth:0,children:[]};
1678 this.nodes.select.all('option').each(function(option, index) {
1679 var rawtext = option.get('innerHTML');
1680 var text = rawtext.replace(/^( )*/, '');
1681 if (rawtext === text) {
1682 text = rawtext.replace(/^(\s)*/, '');
1683 var depth = (rawtext.length - text.length ) + 1;
1685 var depth = ((rawtext.length - text.length )/12)+1;
1687 option.set('innerHTML', text);
1688 options['i'+index] = {text:text,depth:depth,index:index,children:[]};
1691 this.structure = [];
1692 var structcount = 0;
1693 for (var i in options) {
1696 this.structure.push(o);
1700 var current = this.structure[structcount-1];
1701 for (var j = 0; j < o.depth-1;j++) {
1702 if (current && current.children) {
1703 current = current.children[current.children.length-1];
1706 if (current && current.children) {
1707 current.children.push(o);
1712 this.nodes.menu = Y.Node.create(this.generate_menu_content());
1713 this.nodes.menu.one('.smartselect_mask').setStyle('opacity', 0.01);
1714 this.nodes.menu.one('.smartselect_mask').setStyle('width', (this.nodes.select.get('offsetWidth')+5)+'px');
1715 this.nodes.menu.one('.smartselect_mask').setStyle('height', (this.nodes.select.get('offsetHeight'))+'px');
1717 if (this.cfg.mode == null) {
1718 var formwidth = this.nodes.select.ancestor('form').get('offsetWidth');
1719 if (formwidth < 400 || this.nodes.menu.get('offsetWidth') < formwidth*2) {
1720 this.cfg.mode = 'compact';
1722 this.cfg.mode = 'spanning';
1726 if (this.cfg.mode == 'compact') {
1727 this.nodes.menu.addClass('compactmenu');
1729 this.nodes.menu.addClass('spanningmenu');
1730 this.nodes.menu.delegate('mouseover', this.show_sub_menu, '.smartselect_submenuitem', this);
1733 Y.one(document.body).append(this.nodes.menu);
1734 var pos = this.nodes.select.getXY();
1736 this.nodes.menu.setXY(pos);
1737 this.nodes.menu.on('click', this.handle_click, this);
1739 Y.one(window).on('resize', function(){
1740 var pos = this.nodes.select.getXY();
1742 this.nodes.menu.setXY(pos);
1745 generate_menu_content : function() {
1746 var content = '<div id="'+this.id+'_smart_select" class="smartselect">';
1747 content += this.generate_submenu_content(this.structure[0], true);
1748 content += '</ul></div>';
1751 generate_submenu_content : function(item, rootelement) {
1752 this.submenucount++;
1754 if (item.children.length > 0) {
1756 content += '<div class="smartselect_mask" href="#ss_submenu'+this.submenucount+'"> </div>';
1757 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_menu">';
1758 content += '<div class="smartselect_menu_content">';
1760 content += '<li class="smartselect_submenuitem">';
1761 var categoryclass = (this.cfg.selectablecategories)?'selectable':'notselectable';
1762 content += '<a class="smartselect_menuitem_label '+categoryclass+'" href="#ss_submenu'+this.submenucount+'" value="'+item.index+'">'+item.text+'</a>';
1763 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_submenu">';
1764 content += '<div class="smartselect_submenu_content">';
1767 for (var i in item.children) {
1768 content += this.generate_submenu_content(item.children[i],false);
1771 content += '</div>';
1772 content += '</div>';
1778 content += '<li class="smartselect_menuitem">';
1779 content += '<a class="smartselect_menuitem_content selectable" href="#" value="'+item.index+'">'+item.text+'</a>';
1784 select : function(e) {
1787 this.currenttext = t.get('innerHTML');
1788 this.currentvalue = t.getAttribute('value');
1789 this.nodes.select.set('selectedIndex', this.currentvalue);
1792 handle_click : function(e) {
1793 var target = e.target;
1794 if (target.hasClass('smartselect_mask')) {
1796 } else if (target.hasClass('selectable') || target.hasClass('smartselect_menuitem')) {
1798 } else if (target.hasClass('smartselect_menuitem_label') || target.hasClass('smartselect_submenuitem')) {
1799 this.show_sub_menu(e);
1802 show_menu : function(e) {
1804 var menu = e.target.ancestor().one('.smartselect_menu');
1805 menu.addClass('visible');
1806 this.shownevent = Y.one(document.body).on('click', this.hide_menu, this);
1808 show_sub_menu : function(e) {
1810 var target = e.target;
1811 if (!target.hasClass('smartselect_submenuitem')) {
1812 target = target.ancestor('.smartselect_submenuitem');
1814 if (this.cfg.mode == 'compact' && target.one('.smartselect_submenu').hasClass('visible')) {
1815 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1818 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1819 target.one('.smartselect_submenu').addClass('visible');
1821 hide_menu : function() {
1822 this.nodes.menu.all('.visible').removeClass('visible');
1823 if (this.shownevent) {
1824 this.shownevent.detach();
1828 smartselect.init(Y, id, options, {select:select});
1832 /** List of flv players to be loaded */
1833 M.util.video_players = [];
1834 /** List of mp3 players to be loaded */
1835 M.util.audio_players = [];
1839 * @param id element id
1840 * @param fileurl media url
1843 * @param autosize true means detect size from media
1845 M.util.add_video_player = function (id, fileurl, width, height, autosize) {
1846 M.util.video_players.push({id: id, fileurl: fileurl, width: width, height: height, autosize: autosize, resized: false});
1855 M.util.add_audio_player = function (id, fileurl, small) {
1856 M.util.audio_players.push({id: id, fileurl: fileurl, small: small});
1860 * Initialise all audio and video player, must be called from page footer.
1862 M.util.load_flowplayer = function() {
1863 if (M.util.video_players.length == 0 && M.util.audio_players.length == 0) {
1866 if (typeof(flowplayer) == 'undefined') {
1869 var embed_function = function() {
1870 if (loaded || typeof(flowplayer) == 'undefined') {
1878 /* TODO: add CSS color overrides for the flv flow player */
1880 for(var i=0; i<M.util.video_players.length; i++) {
1881 var video = M.util.video_players[i];
1882 if (video.width > 0 && video.height > 0) {
1883 var src = {src: M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.14.swf', width: video.width, height: video.height};
1885 var src = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.14.swf';
1887 flowplayer(video.id, src, {
1888 plugins: {controls: controls},
1890 url: video.fileurl, autoPlay: false, autoBuffering: true, scaling: 'fit', mvideo: video,
1891 onMetaData: function(clip) {
1892 if (clip.mvideo.autosize && !clip.mvideo.resized) {
1893 clip.mvideo.resized = true;
1894 //alert("metadata!!! "+clip.width+' '+clip.height+' '+JSON.stringify(clip.metaData));
1895 if (typeof(clip.metaData.width) == 'undefined' || typeof(clip.metaData.height) == 'undefined') {
1896 // bad luck, we have to guess - we may not get metadata at all
1897 var width = clip.width;
1898 var height = clip.height;
1900 var width = clip.metaData.width;
1901 var height = clip.metaData.height;
1903 var minwidth = 300; // controls are messed up in smaller objects
1904 if (width < minwidth) {
1905 height = (height * minwidth) / width;
1909 var object = this._api();
1910 object.width = width;
1911 object.height = height;
1917 if (M.util.audio_players.length == 0) {
1930 backgroundGradient: [0.5,0,0.3]
1934 for (var j=0; j < document.styleSheets.length; j++) {
1936 // To avoid javascript security violation accessing cross domain stylesheets
1937 var allrules = false;
1939 if (typeof (document.styleSheets[j].rules) != 'undefined') {
1940 allrules = document.styleSheets[j].rules;
1941 } else if (typeof (document.styleSheets[j].cssRules) != 'undefined') {
1942 allrules = document.styleSheets[j].cssRules;
1951 // On cross domain style sheets Chrome V8 allows access to rules but returns null
1956 for(var i=0; i<allrules.length; i++) {
1958 if (/^\.mp3flowplayer_.*Color$/.test(allrules[i].selectorText)) {
1959 if (typeof(allrules[i].cssText) != 'undefined') {
1960 rule = allrules[i].cssText;
1961 } else if (typeof(allrules[i].style.cssText) != 'undefined') {
1962 rule = allrules[i].style.cssText;
1964 if (rule != '' && /.*color\s*:\s*([^;]+).*/gi.test(rule)) {
1965 rule = rule.replace(/.*color\s*:\s*([^;]+).*/gi, '$1');
1966 var colprop = allrules[i].selectorText.replace(/^\.mp3flowplayer_/, '');
1967 controls[colprop] = rule;
1974 for(i=0; i<M.util.audio_players.length; i++) {
1975 var audio = M.util.audio_players[i];
1977 controls.controlall = false;
1978 controls.height = 15;
1979 controls.time = false;
1981 controls.controlall = true;
1982 controls.height = 25;
1983 controls.time = true;
1985 flowplayer(audio.id, M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.14.swf', {
1986 plugins: {controls: controls, audio: {url: M.cfg.wwwroot + '/lib/flowplayer/flowplayer.audio-3.2.10.swf'}},
1987 clip: {url: audio.fileurl, provider: "audio", autoPlay: false}
1992 if (M.cfg.jsrev == -1) {
1993 var jsurl = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.11.js';
1995 var jsurl = M.cfg.wwwroot + '/lib/javascript.php?jsfile=/lib/flowplayer/flowplayer-3.2.11.min.js&rev=' + M.cfg.jsrev;
1997 var fileref = document.createElement('script');
1998 fileref.setAttribute('type','text/javascript');
1999 fileref.setAttribute('src', jsurl);
2000 fileref.onload = embed_function;
2001 fileref.onreadystatechange = embed_function;
2002 document.getElementsByTagName('head')[0].appendChild(fileref);