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 ( Y.one(document.body).hasClass('dir-rtl') ) {
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 ( Y.one(document.body).hasClass('dir-rtl') ) {
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 var val = el.getStyle(prop);
345 val = el.getComputedStyle(prop);
347 return parseInt(val);
350 var resize_object = function() {
351 obj.setStyle('width', '0px');
352 obj.setStyle('height', '0px');
353 var newwidth = get_htmlelement_size('maincontent', 'width') - 35;
355 if (newwidth > 500) {
356 obj.setStyle('width', newwidth + 'px');
358 obj.setStyle('width', '500px');
361 var headerheight = get_htmlelement_size('page-header', 'height');
362 var footerheight = get_htmlelement_size('page-footer', 'height');
363 var newheight = parseInt(Y.one('body').get('winHeight')) - footerheight - headerheight - 100;
364 if (newheight < 400) {
367 obj.setStyle('height', newheight+'px');
371 // fix layout if window resized too
372 window.onresize = function() {
378 * Attach handler to single_select
380 * This code was deprecated in Moodle 2.4 and will be removed in Moodle 2.6
382 * Please see lib/yui/formautosubmit/formautosubmit.js for its replacement
384 M.util.init_select_autosubmit = function(Y, formid, selectid, nothing) {
385 if (M.cfg.developerdebug) {
386 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");
388 Y.use('event-key', function() {
389 var select = Y.one('#'+selectid);
391 // Try to get the form by id
392 var form = Y.one('#'+formid) || (function(){
393 // Hmmm the form's id may have been overriden by an internal input
394 // with the name id which will KILL IE.
395 // We need to manually iterate at this point because if the case
396 // above is true YUI's ancestor method will also kill IE!
398 while (form && form.get('nodeName').toUpperCase() !== 'FORM') {
399 form = form.ancestor();
403 // Make sure we have the form
406 // Create a function to handle our change event
407 var processchange = function(e, paramobject) {
408 if ((nothing===false || select.get('value') != nothing) && paramobject.lastindex != select.get('selectedIndex')) {
409 // chrome doesn't pick up on a click when selecting an element in a select menu, so we use
410 // the on change event to fire this function. This just checks to see if a button was
411 // first pressed before redirecting to the appropriate page.
412 if (Y.UA.os == 'windows' && Y.UA.chrome){
413 if (buttonflag == 1) {
426 var changedown = function(e, paramobject) {
427 if ((nothing===false || select.get('value') != nothing) && paramobject.lastindex != select.get('selectedIndex')) {
428 if(e.keyCode == 13) {
434 var paramobject = new Object();
435 paramobject.lastindex = select.get('selectedIndex');
436 paramobject.eventchangeorblur = select.on('click', processchange, form, paramobject);
437 // Bad hack to circumvent problems with different browsers on different systems.
438 if (Y.UA.os == 'macintosh') {
440 paramobject.eventchangeorblur = select.on('change', processchange, form, paramobject);
442 paramobject.eventkeypress = Y.on('key', processchange, select, 'press:13', form, paramobject);
444 if(Y.UA.os == 'windows' && Y.UA.chrome) {
445 paramobject.eventchangeorblur = select.on('change', processchange, form, paramobject);
447 paramobject.eventkeypress = Y.on('keydown', changedown, select, '', form, paramobject);
455 * Attach handler to url_select
456 * Deprecated from 2.4 onwards.
457 * Please use @see init_select_autosubmit() for redirecting to a url (above).
458 * This function has accessability issues and also does not use the formid passed through as a parameter.
460 M.util.init_url_select = function(Y, formid, selectid, nothing) {
461 if (M.cfg.developerdebug) {
462 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");
464 YUI().use('node', function(Y) {
465 Y.on('change', function() {
466 if ((nothing == false && Y.Lang.isBoolean(nothing)) || Y.one('#'+selectid).get('value') != nothing) {
467 window.location = M.cfg.wwwroot+Y.one('#'+selectid).get('value');
475 * Breaks out all links to the top frame - used in frametop page layout.
477 M.util.init_frametop = function(Y) {
478 Y.all('a').each(function(node) {
479 node.set('target', '_top');
481 Y.all('form').each(function(node) {
482 node.set('target', '_top');
487 * Finds all nodes that match the given CSS selector and attaches events to them
488 * so that they toggle a given classname when clicked.
491 * @param {string} id An id containing elements to target
492 * @param {string} cssselector A selector to use to find targets
493 * @param {string} toggleclassname A classname to toggle
495 M.util.init_toggle_class_on_click = function(Y, id, cssselector, toggleclassname, togglecssselector) {
497 if (togglecssselector == '') {
498 togglecssselector = cssselector;
501 var node = Y.one('#'+id);
502 node.all(cssselector).each(function(n){
503 n.on('click', function(e){
505 if (e.target.test(cssselector) && !e.target.test('a') && !e.target.test('img')) {
506 if (this.test(togglecssselector)) {
507 this.toggleClass(toggleclassname);
509 this.ancestor(togglecssselector).toggleClass(toggleclassname);
514 // Attach this click event to the node rather than all selectors... will be much better
516 node.on('click', function(e){
517 if (e.target.hasClass('addtoall')) {
518 this.all(togglecssselector).addClass(toggleclassname);
519 } else if (e.target.hasClass('removefromall')) {
520 this.all(togglecssselector+'.'+toggleclassname).removeClass(toggleclassname);
526 * Initialises a colour picker
528 * Designed to be used with admin_setting_configcolourpicker although could be used
529 * anywhere, just give a text input an id and insert a div with the class admin_colourpicker
530 * above or below the input (must have the same parent) and then call this with the
533 * This code was mostly taken from my [Sam Hemelryk] css theme tool available in
534 * contrib/blocks. For better docs refer to that.
538 * @param {object} previewconf
540 M.util.init_colour_picker = function(Y, id, previewconf) {
542 * We need node and event-mouseenter
544 Y.use('node', 'event-mouseenter', function(){
546 * The colour picker object
555 eventMouseEnter : null,
556 eventMouseLeave : null,
557 eventMouseMove : null,
562 * Initalises the colour picker by putting everything together and wiring the events
565 this.input = Y.one('#'+id);
566 this.box = this.input.ancestor().one('.admin_colourpicker');
567 this.image = Y.Node.create('<img alt="" class="colourdialogue" />');
568 this.image.setAttribute('src', M.util.image_url('i/colourpicker', 'moodle'));
569 this.preview = Y.Node.create('<div class="previewcolour"></div>');
570 this.preview.setStyle('width', this.height/2).setStyle('height', this.height/2).setStyle('backgroundColor', this.input.get('value'));
571 this.current = Y.Node.create('<div class="currentcolour"></div>');
572 this.current.setStyle('width', this.height/2).setStyle('height', this.height/2 -1).setStyle('backgroundColor', this.input.get('value'));
573 this.box.setContent('').append(this.image).append(this.preview).append(this.current);
575 if (typeof(previewconf) === 'object' && previewconf !== null) {
576 Y.one('#'+id+'_preview').on('click', function(e){
577 if (Y.Lang.isString(previewconf.selector)) {
578 Y.all(previewconf.selector).setStyle(previewconf.style, this.input.get('value'));
580 for (var i in previewconf.selector) {
581 Y.all(previewconf.selector[i]).setStyle(previewconf.style, this.input.get('value'));
587 this.eventClick = this.image.on('click', this.pickColour, this);
588 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
591 * Starts to follow the mouse once it enter the image
593 startFollow : function(e) {
594 this.eventMouseEnter.detach();
595 this.eventMouseLeave = Y.on('mouseleave', this.endFollow, this.image, this);
596 this.eventMouseMove = this.image.on('mousemove', function(e){
597 this.preview.setStyle('backgroundColor', this.determineColour(e));
601 * Stops following the mouse
603 endFollow : function(e) {
604 this.eventMouseMove.detach();
605 this.eventMouseLeave.detach();
606 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
609 * Picks the colour the was clicked on
611 pickColour : function(e) {
612 var colour = this.determineColour(e);
613 this.input.set('value', colour);
614 this.current.setStyle('backgroundColor', colour);
617 * Calculates the colour fromthe given co-ordinates
619 determineColour : function(e) {
620 var eventx = Math.floor(e.pageX-e.target.getX());
621 var eventy = Math.floor(e.pageY-e.target.getY());
623 var imagewidth = this.width;
624 var imageheight = this.height;
625 var factor = this.factor;
626 var colour = [255,0,0];
637 var matrixcount = matrices.length;
638 var limit = Math.round(imagewidth/matrixcount);
639 var heightbreak = Math.round(imageheight/2);
641 for (var x = 0; x < imagewidth; x++) {
642 var divisor = Math.floor(x / limit);
643 var matrix = matrices[divisor];
645 colour[0] += matrix[0]*factor;
646 colour[1] += matrix[1]*factor;
647 colour[2] += matrix[2]*factor;
654 var pixel = [colour[0], colour[1], colour[2]];
655 if (eventy < heightbreak) {
656 pixel[0] += Math.floor(((255-pixel[0])/heightbreak) * (heightbreak - eventy));
657 pixel[1] += Math.floor(((255-pixel[1])/heightbreak) * (heightbreak - eventy));
658 pixel[2] += Math.floor(((255-pixel[2])/heightbreak) * (heightbreak - eventy));
659 } else if (eventy > heightbreak) {
660 pixel[0] = Math.floor((imageheight-eventy)*(pixel[0]/heightbreak));
661 pixel[1] = Math.floor((imageheight-eventy)*(pixel[1]/heightbreak));
662 pixel[2] = Math.floor((imageheight-eventy)*(pixel[2]/heightbreak));
665 return this.convert_rgb_to_hex(pixel);
668 * Converts an RGB value to Hex
670 convert_rgb_to_hex : function(rgb) {
672 var hexchars = "0123456789ABCDEF";
673 for (var i=0; i<3; i++) {
674 var number = Math.abs(rgb[i]);
675 if (number == 0 || isNaN(number)) {
678 hex += hexchars.charAt((number-number%16)/16)+hexchars.charAt(number%16);
685 * Initialise the colour picker :) Hoorah
691 M.util.init_block_hider = function(Y, config) {
692 Y.use('base', 'node', function(Y) {
693 M.util.block_hider = M.util.block_hider || (function(){
694 var blockhider = function() {
695 blockhider.superclass.constructor.apply(this, arguments);
697 blockhider.prototype = {
698 initializer : function(config) {
699 this.set('block', '#'+this.get('id'));
700 var b = this.get('block'),
703 if (t && (a = t.one('.block_action'))) {
704 var hide = Y.Node.create('<img class="block-hider-hide" tabindex="0" alt="'+config.tooltipVisible+'" title="'+config.tooltipVisible+'" />');
705 hide.setAttribute('src', this.get('iconVisible')).on('click', this.updateState, this, true);
706 hide.on('keypress', this.updateStateKey, this, true);
707 var show = Y.Node.create('<img class="block-hider-show" tabindex="0" alt="'+config.tooltipHidden+'" title="'+config.tooltipHidden+'" />');
708 show.setAttribute('src', this.get('iconHidden')).on('click', this.updateState, this, false);
709 show.on('keypress', this.updateStateKey, this, false);
710 a.insert(show, 0).insert(hide, 0);
713 updateState : function(e, hide) {
714 M.util.set_user_preference(this.get('preference'), hide);
716 this.get('block').addClass('hidden');
718 this.get('block').removeClass('hidden');
721 updateStateKey : function(e, hide) {
722 if (e.keyCode == 13) { //allow hide/show via enter key
723 this.updateState(this, hide);
727 Y.extend(blockhider, Y.Base, blockhider.prototype, {
733 value : M.util.image_url('t/switch_minus', 'moodle')
736 value : M.util.image_url('t/switch_plus', 'moodle')
739 setter : function(node) {
747 new M.util.block_hider(config);
752 * Returns a string registered in advance for usage in JavaScript
754 * If you do not pass the third parameter, the function will just return
755 * the corresponding value from the M.str object. If the third parameter is
756 * provided, the function performs {$a} placeholder substitution in the
757 * same way as PHP get_string() in Moodle does.
759 * @param {String} identifier string identifier
760 * @param {String} component the component providing the string
761 * @param {Object|String} a optional variable to populate placeholder with
763 M.util.get_string = function(identifier, component, a) {
766 if (M.cfg.developerdebug) {
767 // creating new instance if YUI is not optimal but it seems to be better way then
768 // require the instance via the function API - note that it is used in rare cases
769 // for debugging only anyway
770 // To ensure we don't kill browser performance if hundreds of get_string requests
771 // are made we cache the instance we generate within the M.util namespace.
772 // We don't publicly define the variable so that it doesn't get abused.
773 if (typeof M.util.get_string_yui_instance === 'undefined') {
774 M.util.get_string_yui_instance = new YUI({ debug : true });
776 var Y = M.util.get_string_yui_instance;
779 if (!M.str.hasOwnProperty(component) || !M.str[component].hasOwnProperty(identifier)) {
780 stringvalue = '[[' + identifier + ',' + component + ']]';
781 if (M.cfg.developerdebug) {
782 Y.log('undefined string ' + stringvalue, 'warn', 'M.util.get_string');
787 stringvalue = M.str[component][identifier];
789 if (typeof a == 'undefined') {
790 // no placeholder substitution requested
794 if (typeof a == 'number' || typeof a == 'string') {
795 // replace all occurrences of {$a} with the placeholder value
796 stringvalue = stringvalue.replace(/\{\$a\}/g, a);
800 if (typeof a == 'object') {
801 // replace {$a->key} placeholders
803 if (typeof a[key] != 'number' && typeof a[key] != 'string') {
804 if (M.cfg.developerdebug) {
805 Y.log('invalid value type for $a->' + key, 'warn', 'M.util.get_string');
809 var search = '{$a->' + key + '}';
810 search = search.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
811 search = new RegExp(search, 'g');
812 stringvalue = stringvalue.replace(search, a[key]);
817 if (M.cfg.developerdebug) {
818 Y.log('incorrect placeholder type', 'warn', 'M.util.get_string');
824 * Set focus on username or password field of the login form
826 M.util.focus_login_form = function(Y) {
827 var username = Y.one('#username');
828 var password = Y.one('#password');
830 if (username == null || password == null) {
831 // something is wrong here
835 var curElement = document.activeElement
836 if (curElement == 'undefined') {
837 // legacy browser - skip refocus protection
838 } else if (curElement.tagName == 'INPUT') {
839 // user was probably faster to focus something, do not mess with focus
843 if (username.get('value') == '') {
851 * Adds lightbox hidden element that covers the whole node.
854 * @param {Node} the node lightbox should be added to
855 * @retun {Node} created lightbox node
857 M.util.add_lightbox = function(Y, node) {
858 var WAITICON = {'pix':"i/loading_small",'component':'moodle'};
860 // Check if lightbox is already there
861 if (node.one('.lightbox')) {
862 return node.one('.lightbox');
865 node.setStyle('position', 'relative');
866 var waiticon = Y.Node.create('<img />')
868 'src' : M.util.image_url(WAITICON.pix, WAITICON.component)
871 'position' : 'relative',
875 var lightbox = Y.Node.create('<div></div>')
878 'position' : 'absolute',
883 'backgroundColor' : 'white',
884 'text-align' : 'center'
886 .setAttribute('class', 'lightbox')
889 lightbox.appendChild(waiticon);
890 node.append(lightbox);
895 * Appends a hidden spinner element to the specified node.
898 * @param {Node} the node the spinner should be added to
899 * @return {Node} created spinner node
901 M.util.add_spinner = function(Y, node) {
902 var WAITICON = {'pix':"i/loading_small",'component':'moodle'};
904 // Check if spinner is already there
905 if (node.one('.spinner')) {
906 return node.one('.spinner');
909 var spinner = Y.Node.create('<img />')
910 .setAttribute('src', M.util.image_url(WAITICON.pix, WAITICON.component))
912 .addClass('iconsmall')
915 node.append(spinner);
919 //=== old legacy JS code, hopefully to be replaced soon by M.xx.yy and YUI3 code ===
921 function checkall() {
922 var inputs = document.getElementsByTagName('input');
923 for (var i = 0; i < inputs.length; i++) {
924 if (inputs[i].type == 'checkbox') {
925 if (inputs[i].disabled || inputs[i].readOnly) {
928 inputs[i].checked = true;
933 function checknone() {
934 var inputs = document.getElementsByTagName('input');
935 for (var i = 0; i < inputs.length; i++) {
936 if (inputs[i].type == 'checkbox') {
937 if (inputs[i].disabled || inputs[i].readOnly) {
940 inputs[i].checked = false;
946 * Either check, or uncheck, all checkboxes inside the element with id is
947 * @param id the id of the container
948 * @param checked the new state, either '' or 'checked'.
950 function select_all_in_element_with_id(id, checked) {
951 var container = document.getElementById(id);
955 var inputs = container.getElementsByTagName('input');
956 for (var i = 0; i < inputs.length; ++i) {
957 if (inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
958 inputs[i].checked = checked;
963 function select_all_in(elTagName, elClass, elId) {
964 var inputs = document.getElementsByTagName('input');
965 inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
966 for(var i = 0; i < inputs.length; ++i) {
967 if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
968 inputs[i].checked = 'checked';
973 function deselect_all_in(elTagName, elClass, elId) {
974 var inputs = document.getElementsByTagName('INPUT');
975 inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
976 for(var i = 0; i < inputs.length; ++i) {
977 if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
978 inputs[i].checked = '';
983 function confirm_if(expr, message) {
987 return confirm(message);
992 findParentNode (start, elementName, elementClass, elementID)
994 Travels up the DOM hierarchy to find a parent element with the
995 specified tag name, class, and id. All conditions must be met,
996 but any can be ommitted. Returns the BODY element if no match
999 function findParentNode(el, elName, elClass, elId) {
1000 while (el.nodeName.toUpperCase() != 'BODY') {
1001 if ((!elName || el.nodeName.toUpperCase() == elName) &&
1002 (!elClass || el.className.indexOf(elClass) != -1) &&
1003 (!elId || el.id == elId)) {
1011 findChildNode (start, elementName, elementClass, elementID)
1013 Travels down the DOM hierarchy to find all child elements with the
1014 specified tag name, class, and id. All conditions must be met,
1015 but any can be ommitted.
1016 Doesn't examine children of matches.
1018 function findChildNodes(start, tagName, elementClass, elementID, elementName) {
1019 var children = new Array();
1020 for (var i = 0; i < start.childNodes.length; i++) {
1021 var classfound = false;
1022 var child = start.childNodes[i];
1023 if((child.nodeType == 1) &&//element node type
1024 (elementClass && (typeof(child.className)=='string'))) {
1025 var childClasses = child.className.split(/\s+/);
1026 for (var childClassIndex in childClasses) {
1027 if (childClasses[childClassIndex]==elementClass) {
1033 if(child.nodeType == 1) { //element node type
1034 if ( (!tagName || child.nodeName == tagName) &&
1035 (!elementClass || classfound)&&
1036 (!elementID || child.id == elementID) &&
1037 (!elementName || child.name == elementName))
1039 children = children.concat(child);
1041 children = children.concat(findChildNodes(child, tagName, elementClass, elementID, elementName));
1048 function unmaskPassword(id) {
1049 var pw = document.getElementById(id);
1050 var chb = document.getElementById(id+'unmask');
1053 // first try IE way - it can not set name attribute later
1055 var newpw = document.createElement('<input type="text" autocomplete="off" name="'+pw.name+'">');
1057 var newpw = document.createElement('<input type="password" autocomplete="off" name="'+pw.name+'">');
1059 newpw.attributes['class'].nodeValue = pw.attributes['class'].nodeValue;
1061 var newpw = document.createElement('input');
1062 newpw.setAttribute('autocomplete', 'off');
1063 newpw.setAttribute('name', pw.name);
1065 newpw.setAttribute('type', 'text');
1067 newpw.setAttribute('type', 'password');
1069 newpw.setAttribute('class', pw.getAttribute('class'));
1072 newpw.size = pw.size;
1073 newpw.onblur = pw.onblur;
1074 newpw.onchange = pw.onchange;
1075 newpw.value = pw.value;
1076 pw.parentNode.replaceChild(newpw, pw);
1079 function filterByParent(elCollection, parentFinder) {
1080 var filteredCollection = [];
1081 for (var i = 0; i < elCollection.length; ++i) {
1082 var findParent = parentFinder(elCollection[i]);
1083 if (findParent.nodeName.toUpperCase() != 'BODY') {
1084 filteredCollection.push(elCollection[i]);
1087 return filteredCollection;
1091 All this is here just so that IE gets to handle oversized blocks
1092 in a visually pleasing manner. It does a browser detect. So sue me.
1095 function fix_column_widths() {
1096 var agt = navigator.userAgent.toLowerCase();
1097 if ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1)) {
1098 fix_column_width('left-column');
1099 fix_column_width('right-column');
1103 function fix_column_width(colName) {
1104 if(column = document.getElementById(colName)) {
1105 if(!column.offsetWidth) {
1106 setTimeout("fix_column_width('" + colName + "')", 20);
1111 var nodes = column.childNodes;
1113 for(i = 0; i < nodes.length; ++i) {
1114 if(nodes[i].className.indexOf("block") != -1 ) {
1115 if(width < nodes[i].offsetWidth) {
1116 width = nodes[i].offsetWidth;
1121 for(i = 0; i < nodes.length; ++i) {
1122 if(nodes[i].className.indexOf("block") != -1 ) {
1123 nodes[i].style.width = width + 'px';
1131 Insert myValue at current cursor position
1133 function insertAtCursor(myField, myValue) {
1135 if (document.selection) {
1137 sel = document.selection.createRange();
1140 // Mozilla/Netscape support
1141 else if (myField.selectionStart || myField.selectionStart == '0') {
1142 var startPos = myField.selectionStart;
1143 var endPos = myField.selectionEnd;
1144 myField.value = myField.value.substring(0, startPos)
1145 + myValue + myField.value.substring(endPos, myField.value.length);
1147 myField.value += myValue;
1153 Call instead of setting window.onload directly or setting body onload=.
1154 Adds your function to a chain of functions rather than overwriting anything
1157 function addonload(fn) {
1158 var oldhandler=window.onload;
1159 window.onload=function() {
1160 if(oldhandler) oldhandler();
1165 * Replacement for getElementsByClassName in browsers that aren't cool enough
1167 * Relying on the built-in getElementsByClassName is far, far faster than
1170 * Note: the third argument used to be an object with odd behaviour. It now
1171 * acts like the 'name' in the HTML5 spec, though the old behaviour is still
1172 * mimicked if you pass an object.
1174 * @param {Node} oElm The top-level node for searching. To search a whole
1175 * document, use `document`.
1176 * @param {String} strTagName filter by tag names
1177 * @param {String} name same as HTML5 spec
1179 function getElementsByClassName(oElm, strTagName, name) {
1180 // for backwards compatibility
1181 if(typeof name == "object") {
1182 var names = new Array();
1183 for(var i=0; i<name.length; i++) names.push(names[i]);
1184 name = names.join('');
1186 // use native implementation if possible
1187 if (oElm.getElementsByClassName && Array.filter) {
1188 if (strTagName == '*') {
1189 return oElm.getElementsByClassName(name);
1191 return Array.filter(oElm.getElementsByClassName(name), function(el) {
1192 return el.nodeName.toLowerCase() == strTagName.toLowerCase();
1196 // native implementation unavailable, fall back to slow method
1197 var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
1198 var arrReturnElements = new Array();
1199 var arrRegExpClassNames = new Array();
1200 var names = name.split(' ');
1201 for(var i=0; i<names.length; i++) {
1202 arrRegExpClassNames.push(new RegExp("(^|\\s)" + names[i].replace(/\-/g, "\\-") + "(\\s|$)"));
1206 for(var j=0; j<arrElements.length; j++) {
1207 oElement = arrElements[j];
1209 for(var k=0; k<arrRegExpClassNames.length; k++) {
1210 if(!arrRegExpClassNames[k].test(oElement.className)) {
1211 bMatchesAll = false;
1216 arrReturnElements.push(oElement);
1219 return (arrReturnElements)
1222 function openpopup(event, args) {
1225 if (event.preventDefault) {
1226 event.preventDefault();
1228 event.returnValue = false;
1232 // Make sure the name argument is set and valid.
1233 var nameregex = /[^a-z0-9_]/i;
1234 if (typeof args.name !== 'string') {
1235 args.name = '_blank';
1236 } else if (args.name.match(nameregex)) {
1237 // Cleans window name because IE does not support funky ones.
1238 args.name = args.name.replace(nameregex, '_');
1239 if (M.cfg.developerdebug) {
1240 alert('DEVELOPER NOTICE: Invalid \'name\' passed to openpopup()');
1244 var fullurl = args.url;
1245 if (!args.url.match(/https?:\/\//)) {
1246 fullurl = M.cfg.wwwroot + args.url;
1248 if (args.fullscreen) {
1249 args.options = args.options.
1250 replace(/top=\d+/, 'top=0').
1251 replace(/left=\d+/, 'left=0').
1252 replace(/width=\d+/, 'width=' + screen.availWidth).
1253 replace(/height=\d+/, 'height=' + screen.availHeight);
1255 var windowobj = window.open(fullurl,args.name,args.options);
1260 if (args.fullscreen) {
1261 // In some browser / OS combinations (E.g. Chrome on Windows), the
1262 // window initially opens slighly too big. The width and heigh options
1263 // seem to control the area inside the browser window, so what with
1264 // scroll-bars, etc. the actual window is bigger than the screen.
1265 // Therefore, we need to fix things up after the window is open.
1266 var hackcount = 100;
1267 var get_size_exactly_right = function() {
1268 windowobj.moveTo(0, 0);
1269 windowobj.resizeTo(screen.availWidth, screen.availHeight);
1271 // Unfortunately, it seems that in Chrome on Ubuntu, if you call
1272 // something like windowobj.resizeTo(1280, 1024) too soon (up to
1273 // about 50ms) after the window is open, then it actually behaves
1274 // as if you called windowobj.resizeTo(0, 0). Therefore, we need to
1275 // check that the resize actually worked, and if not, repeatedly try
1276 // again after a short delay until it works (but with a limit of
1277 // hackcount repeats.
1278 if (hackcount > 0 && (windowobj.innerHeight < 10 || windowobj.innerWidth < 10)) {
1280 setTimeout(get_size_exactly_right, 10);
1283 setTimeout(get_size_exactly_right, 0);
1290 /** Close the current browser window. */
1291 function close_window(e) {
1292 if (e.preventDefault) {
1295 e.returnValue = false;
1301 * Used in a couple of modules to hide navigation areas when using AJAX
1304 function show_item(itemid) {
1305 var item = document.getElementById(itemid);
1307 item.style.display = "";
1311 function destroy_item(itemid) {
1312 var item = document.getElementById(itemid);
1314 item.parentNode.removeChild(item);
1318 * Tranfer keyboard focus to the HTML element with the given id, if it exists.
1319 * @param controlid the control id.
1321 function focuscontrol(controlid) {
1322 var control = document.getElementById(controlid);
1329 * Transfers keyboard focus to an HTML element based on the old style style of focus
1330 * This function should be removed as soon as it is no longer used
1332 function old_onload_focus(formid, controlname) {
1333 if (document.forms[formid] && document.forms[formid].elements && document.forms[formid].elements[controlname]) {
1334 document.forms[formid].elements[controlname].focus();
1338 function build_querystring(obj) {
1339 return convert_object_to_string(obj, '&');
1342 function build_windowoptionsstring(obj) {
1343 return convert_object_to_string(obj, ',');
1346 function convert_object_to_string(obj, separator) {
1347 if (typeof obj !== 'object') {
1352 k = encodeURIComponent(k);
1354 if(obj[k] instanceof Array) {
1355 for(var i in value) {
1356 list.push(k+'[]='+encodeURIComponent(value[i]));
1359 list.push(k+'='+encodeURIComponent(value));
1362 return list.join(separator);
1365 function stripHTML(str) {
1366 var re = /<\S[^><]*>/g;
1367 var ret = str.replace(re, "");
1371 Number.prototype.fixed=function(n){
1373 return round(Number(this)*pow(10,n))/pow(10,n);
1375 function update_progress_bar (id, width, pt, msg, es){
1377 var status = document.getElementById("status_"+id);
1378 var percent_indicator = document.getElementById("pt_"+id);
1379 var progress_bar = document.getElementById("progress_"+id);
1380 var time_es = document.getElementById("time_"+id);
1381 status.innerHTML = msg;
1382 percent_indicator.innerHTML = percent.fixed(2) + '%';
1383 if(percent == 100) {
1384 progress_bar.style.background = "green";
1385 time_es.style.display = "none";
1387 progress_bar.style.background = "#FFCC66";
1389 time_es.innerHTML = "";
1391 time_es.innerHTML = es.fixed(2)+" sec";
1392 time_es.style.display
1396 progress_bar.style.width = width + "px";
1401 // ===== Deprecated core Javascript functions for Moodle ====
1402 // DO NOT USE!!!!!!!
1403 // Do not put this stuff in separate file because it only adds extra load on servers!
1406 * Used in a couple of modules to hide navigation areas when using AJAX
1408 function hide_item(itemid) {
1409 // use class='hiddenifjs' instead
1410 var item = document.getElementById(itemid);
1412 item.style.display = "none";
1416 M.util.help_icon = {
1419 add : function(Y, properties) {
1421 properties.node = Y.one('#'+properties.id);
1422 if (properties.node) {
1423 properties.node.on('click', this.display, this, properties);
1426 display : function(event, args) {
1427 event.preventDefault();
1428 if (M.util.help_icon.instance === null) {
1429 var Y = M.util.help_icon.Y;
1430 Y.use('overlay', 'io-base', 'event-mouseenter', 'node', 'event-key', function(Y) {
1431 var help_content_overlay = {
1436 var closebtn = Y.Node.create('<a id="closehelpbox" href="#"><img src="'+M.util.image_url('t/delete', 'moodle')+'" /></a>');
1437 // Create an overlay from markup
1438 this.overlay = new Y.Overlay({
1439 headerContent: closebtn,
1446 this.overlay.render(Y.one(document.body));
1448 closebtn.on('click', this.overlay.hide, this.overlay);
1450 var boundingBox = this.overlay.get("boundingBox");
1452 // Hide the menu if the user clicks outside of its content
1453 boundingBox.get("ownerDocument").on("mousedown", function (event) {
1454 var oTarget = event.target;
1455 var menuButton = Y.one("#"+args.id);
1457 if (!oTarget.compareTo(menuButton) &&
1458 !menuButton.contains(oTarget) &&
1459 !oTarget.compareTo(boundingBox) &&
1460 !boundingBox.contains(oTarget)) {
1461 this.overlay.hide();
1465 Y.on("key", this.close, closebtn , "down:13", this);
1466 closebtn.on('click', this.close, this);
1469 close : function(e) {
1471 this.helplink.focus();
1472 this.overlay.hide();
1475 display : function(event, args) {
1476 this.helplink = args.node;
1477 this.overlay.set('bodyContent', Y.Node.create('<img src="'+M.cfg.loadingicon+'" class="spinner" />'));
1478 this.overlay.set("align", {node:args.node, points:[Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.RC]});
1480 var fullurl = args.url;
1481 if (!args.url.match(/https?:\/\//)) {
1482 fullurl = M.cfg.wwwroot + args.url;
1485 var ajaxurl = fullurl + '&ajax=1';
1491 success: function(id, o, node) {
1492 this.display_callback(o.responseText);
1494 failure: function(id, o, node) {
1495 var debuginfo = o.statusText;
1496 if (M.cfg.developerdebug) {
1497 o.statusText += ' (' + ajaxurl + ')';
1499 this.display_callback('bodyContent',debuginfo);
1505 this.overlay.show();
1507 Y.one('#closehelpbox').focus();
1510 display_callback : function(content) {
1511 content = '<div role="alert">' + content + '</div>';
1512 this.overlay.set('bodyContent', content);
1515 hideContent : function() {
1517 help.overlay.hide();
1520 help_content_overlay.init();
1521 M.util.help_icon.instance = help_content_overlay;
1522 M.util.help_icon.instance.display(event, args);
1525 M.util.help_icon.instance.display(event, args);
1528 init : function(Y) {
1534 * Custom menu namespace
1536 M.core_custom_menu = {
1538 * This method is used to initialise a custom menu given the id that belongs
1539 * to the custom menu's root node.
1542 * @param {string} nodeid
1544 init : function(Y, nodeid) {
1545 var node = Y.one('#'+nodeid);
1547 Y.use('node-menunav', function(Y) {
1549 // Remove the javascript-disabled class.... obviously javascript is enabled.
1550 node.removeClass('javascript-disabled');
1551 // Initialise the menunav plugin
1552 node.plug(Y.Plugin.NodeMenuNav);
1559 * Used to store form manipulation methods and enhancments
1561 M.form = M.form || {};
1564 * Converts a nbsp indented select box into a multi drop down custom control much
1565 * like the custom menu. It also selectable categories on or off.
1567 * $form->init_javascript_enhancement('elementname','smartselect', array('selectablecategories'=>true|false, 'mode'=>'compact'|'spanning'));
1570 * @param {string} id
1571 * @param {Array} options
1573 M.form.init_smartselect = function(Y, id, options) {
1574 if (!id.match(/^id_/)) {
1577 var select = Y.one('select#'+id);
1581 Y.use('event-delegate',function(){
1587 currentvalue : null,
1591 selectablecategories : true,
1599 init : function(Y, id, args, nodes) {
1600 if (typeof(args)=='object') {
1601 for (var i in this.cfg) {
1602 if (args[i] || args[i]===false) {
1603 this.cfg[i] = args[i];
1608 // Display a loading message first up
1609 this.nodes.select = nodes.select;
1611 this.currentvalue = this.nodes.select.get('selectedIndex');
1612 this.currenttext = this.nodes.select.all('option').item(this.currentvalue).get('innerHTML');
1614 var options = Array();
1615 options[''] = {text:this.currenttext,value:'',depth:0,children:[]};
1616 this.nodes.select.all('option').each(function(option, index) {
1617 var rawtext = option.get('innerHTML');
1618 var text = rawtext.replace(/^( )*/, '');
1619 if (rawtext === text) {
1620 text = rawtext.replace(/^(\s)*/, '');
1621 var depth = (rawtext.length - text.length ) + 1;
1623 var depth = ((rawtext.length - text.length )/12)+1;
1625 option.set('innerHTML', text);
1626 options['i'+index] = {text:text,depth:depth,index:index,children:[]};
1629 this.structure = [];
1630 var structcount = 0;
1631 for (var i in options) {
1634 this.structure.push(o);
1638 var current = this.structure[structcount-1];
1639 for (var j = 0; j < o.depth-1;j++) {
1640 if (current && current.children) {
1641 current = current.children[current.children.length-1];
1644 if (current && current.children) {
1645 current.children.push(o);
1650 this.nodes.menu = Y.Node.create(this.generate_menu_content());
1651 this.nodes.menu.one('.smartselect_mask').setStyle('opacity', 0.01);
1652 this.nodes.menu.one('.smartselect_mask').setStyle('width', (this.nodes.select.get('offsetWidth')+5)+'px');
1653 this.nodes.menu.one('.smartselect_mask').setStyle('height', (this.nodes.select.get('offsetHeight'))+'px');
1655 if (this.cfg.mode == null) {
1656 var formwidth = this.nodes.select.ancestor('form').get('offsetWidth');
1657 if (formwidth < 400 || this.nodes.menu.get('offsetWidth') < formwidth*2) {
1658 this.cfg.mode = 'compact';
1660 this.cfg.mode = 'spanning';
1664 if (this.cfg.mode == 'compact') {
1665 this.nodes.menu.addClass('compactmenu');
1667 this.nodes.menu.addClass('spanningmenu');
1668 this.nodes.menu.delegate('mouseover', this.show_sub_menu, '.smartselect_submenuitem', this);
1671 Y.one(document.body).append(this.nodes.menu);
1672 var pos = this.nodes.select.getXY();
1674 this.nodes.menu.setXY(pos);
1675 this.nodes.menu.on('click', this.handle_click, this);
1677 Y.one(window).on('resize', function(){
1678 var pos = this.nodes.select.getXY();
1680 this.nodes.menu.setXY(pos);
1683 generate_menu_content : function() {
1684 var content = '<div id="'+this.id+'_smart_select" class="smartselect">';
1685 content += this.generate_submenu_content(this.structure[0], true);
1686 content += '</ul></div>';
1689 generate_submenu_content : function(item, rootelement) {
1690 this.submenucount++;
1692 if (item.children.length > 0) {
1694 content += '<div class="smartselect_mask" href="#ss_submenu'+this.submenucount+'"> </div>';
1695 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_menu">';
1696 content += '<div class="smartselect_menu_content">';
1698 content += '<li class="smartselect_submenuitem">';
1699 var categoryclass = (this.cfg.selectablecategories)?'selectable':'notselectable';
1700 content += '<a class="smartselect_menuitem_label '+categoryclass+'" href="#ss_submenu'+this.submenucount+'" value="'+item.index+'">'+item.text+'</a>';
1701 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_submenu">';
1702 content += '<div class="smartselect_submenu_content">';
1705 for (var i in item.children) {
1706 content += this.generate_submenu_content(item.children[i],false);
1709 content += '</div>';
1710 content += '</div>';
1716 content += '<li class="smartselect_menuitem">';
1717 content += '<a class="smartselect_menuitem_content selectable" href="#" value="'+item.index+'">'+item.text+'</a>';
1722 select : function(e) {
1725 this.currenttext = t.get('innerHTML');
1726 this.currentvalue = t.getAttribute('value');
1727 this.nodes.select.set('selectedIndex', this.currentvalue);
1730 handle_click : function(e) {
1731 var target = e.target;
1732 if (target.hasClass('smartselect_mask')) {
1734 } else if (target.hasClass('selectable') || target.hasClass('smartselect_menuitem')) {
1736 } else if (target.hasClass('smartselect_menuitem_label') || target.hasClass('smartselect_submenuitem')) {
1737 this.show_sub_menu(e);
1740 show_menu : function(e) {
1742 var menu = e.target.ancestor().one('.smartselect_menu');
1743 menu.addClass('visible');
1744 this.shownevent = Y.one(document.body).on('click', this.hide_menu, this);
1746 show_sub_menu : function(e) {
1748 var target = e.target;
1749 if (!target.hasClass('smartselect_submenuitem')) {
1750 target = target.ancestor('.smartselect_submenuitem');
1752 if (this.cfg.mode == 'compact' && target.one('.smartselect_submenu').hasClass('visible')) {
1753 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1756 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1757 target.one('.smartselect_submenu').addClass('visible');
1759 hide_menu : function() {
1760 this.nodes.menu.all('.visible').removeClass('visible');
1761 if (this.shownevent) {
1762 this.shownevent.detach();
1766 smartselect.init(Y, id, options, {select:select});
1770 /** List of flv players to be loaded */
1771 M.util.video_players = [];
1772 /** List of mp3 players to be loaded */
1773 M.util.audio_players = [];
1777 * @param id element id
1778 * @param fileurl media url
1781 * @param autosize true means detect size from media
1783 M.util.add_video_player = function (id, fileurl, width, height, autosize) {
1784 M.util.video_players.push({id: id, fileurl: fileurl, width: width, height: height, autosize: autosize, resized: false});
1793 M.util.add_audio_player = function (id, fileurl, small) {
1794 M.util.audio_players.push({id: id, fileurl: fileurl, small: small});
1798 * Initialise all audio and video player, must be called from page footer.
1800 M.util.load_flowplayer = function() {
1801 if (M.util.video_players.length == 0 && M.util.audio_players.length == 0) {
1804 if (typeof(flowplayer) == 'undefined') {
1807 var embed_function = function() {
1808 if (loaded || typeof(flowplayer) == 'undefined') {
1816 /* TODO: add CSS color overrides for the flv flow player */
1818 for(var i=0; i<M.util.video_players.length; i++) {
1819 var video = M.util.video_players[i];
1820 if (video.width > 0 && video.height > 0) {
1821 var src = {src: M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.14.swf', width: video.width, height: video.height};
1823 var src = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.14.swf';
1825 flowplayer(video.id, src, {
1826 plugins: {controls: controls},
1828 url: video.fileurl, autoPlay: false, autoBuffering: true, scaling: 'fit', mvideo: video,
1829 onMetaData: function(clip) {
1830 if (clip.mvideo.autosize && !clip.mvideo.resized) {
1831 clip.mvideo.resized = true;
1832 //alert("metadata!!! "+clip.width+' '+clip.height+' '+JSON.stringify(clip.metaData));
1833 if (typeof(clip.metaData.width) == 'undefined' || typeof(clip.metaData.height) == 'undefined') {
1834 // bad luck, we have to guess - we may not get metadata at all
1835 var width = clip.width;
1836 var height = clip.height;
1838 var width = clip.metaData.width;
1839 var height = clip.metaData.height;
1841 var minwidth = 300; // controls are messed up in smaller objects
1842 if (width < minwidth) {
1843 height = (height * minwidth) / width;
1847 var object = this._api();
1848 object.width = width;
1849 object.height = height;
1855 if (M.util.audio_players.length == 0) {
1868 backgroundGradient: [0.5,0,0.3]
1872 for (var j=0; j < document.styleSheets.length; j++) {
1874 // To avoid javascript security violation accessing cross domain stylesheets
1875 var allrules = false;
1877 if (typeof (document.styleSheets[j].rules) != 'undefined') {
1878 allrules = document.styleSheets[j].rules;
1879 } else if (typeof (document.styleSheets[j].cssRules) != 'undefined') {
1880 allrules = document.styleSheets[j].cssRules;
1889 // On cross domain style sheets Chrome V8 allows access to rules but returns null
1894 for(var i=0; i<allrules.length; i++) {
1896 if (/^\.mp3flowplayer_.*Color$/.test(allrules[i].selectorText)) {
1897 if (typeof(allrules[i].cssText) != 'undefined') {
1898 rule = allrules[i].cssText;
1899 } else if (typeof(allrules[i].style.cssText) != 'undefined') {
1900 rule = allrules[i].style.cssText;
1902 if (rule != '' && /.*color\s*:\s*([^;]+).*/gi.test(rule)) {
1903 rule = rule.replace(/.*color\s*:\s*([^;]+).*/gi, '$1');
1904 var colprop = allrules[i].selectorText.replace(/^\.mp3flowplayer_/, '');
1905 controls[colprop] = rule;
1912 for(i=0; i<M.util.audio_players.length; i++) {
1913 var audio = M.util.audio_players[i];
1915 controls.controlall = false;
1916 controls.height = 15;
1917 controls.time = false;
1919 controls.controlall = true;
1920 controls.height = 25;
1921 controls.time = true;
1923 flowplayer(audio.id, M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.14.swf', {
1924 plugins: {controls: controls, audio: {url: M.cfg.wwwroot + '/lib/flowplayer/flowplayer.audio-3.2.10.swf'}},
1925 clip: {url: audio.fileurl, provider: "audio", autoPlay: false}
1930 if (M.cfg.jsrev == -1) {
1931 var jsurl = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.11.js';
1933 var jsurl = M.cfg.wwwroot + '/lib/javascript.php?jsfile=/lib/flowplayer/flowplayer-3.2.11.min.js&rev=' + M.cfg.jsrev;
1935 var fileref = document.createElement('script');
1936 fileref.setAttribute('type','text/javascript');
1937 fileref.setAttribute('src', jsurl);
1938 fileref.onload = embed_function;
1939 fileref.onreadystatechange = embed_function;
1940 document.getElementsByTagName('head')[0].appendChild(fileref);