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 M.util.init_select_autosubmit = function(Y, formid, selectid, nothing) {
381 Y.use('event-key', function() {
382 var select = Y.one('#'+selectid);
384 // Try to get the form by id
385 var form = Y.one('#'+formid) || (function(){
386 // Hmmm the form's id may have been overriden by an internal input
387 // with the name id which will KILL IE.
388 // We need to manually iterate at this point because if the case
389 // above is true YUI's ancestor method will also kill IE!
391 while (form && form.get('nodeName').toUpperCase() !== 'FORM') {
392 form = form.ancestor();
396 // Make sure we have the form
399 // Create a function to handle our change event
400 var processchange = function(e, paramobject) {
401 if ((nothing===false || select.get('value') != nothing) && paramobject.lastindex != select.get('selectedIndex')) {
402 // chrome doesn't pick up on a click when selecting an element in a select menu, so we use
403 // the on change event to fire this function. This just checks to see if a button was
404 // first pressed before redirecting to the appropriate page.
405 if (Y.UA.os == 'windows' && Y.UA.chrome){
406 if (buttonflag == 1) {
417 paramobject.lastindex = select.get('selectedIndex');
420 var changedown = function(e, paramobject) {
421 if ((nothing===false || select.get('value') != nothing) && paramobject.lastindex != select.get('selectedIndex')) {
422 if(e.keyCode == 13) {
425 paramobject.lastindex = select.get('selectedIndex');
429 var paramobject = new Object();
430 paramobject.lastindex = select.get('selectedIndex');
431 paramobject.eventchangeorblur = select.on('click', processchange, form, paramobject);
432 // Bad hack to circumvent problems with different browsers on different systems.
433 if (Y.UA.os == 'macintosh') {
435 paramobject.eventchangeorblur = select.on('change', processchange, form, paramobject);
437 paramobject.eventkeypress = Y.on('key', processchange, select, 'press:13', form, paramobject);
439 if(Y.UA.os == 'windows' && Y.UA.chrome) {
440 paramobject.eventchangeorblur = select.on('change', processchange, form, paramobject);
442 paramobject.eventkeypress = Y.on('keydown', changedown, select, '', form, paramobject);
450 * Attach handler to url_select
451 * Deprecated from 2.4 onwards.
452 * Please use @see init_select_autosubmit() for redirecting to a url (above).
453 * This function has accessability issues and also does not use the formid passed through as a parameter.
455 M.util.init_url_select = function(Y, formid, selectid, nothing) {
456 YUI().use('node', function(Y) {
457 Y.on('change', function() {
458 if ((nothing == false && Y.Lang.isBoolean(nothing)) || Y.one('#'+selectid).get('value') != nothing) {
459 window.location = M.cfg.wwwroot+Y.one('#'+selectid).get('value');
467 * Breaks out all links to the top frame - used in frametop page layout.
469 M.util.init_frametop = function(Y) {
470 Y.all('a').each(function(node) {
471 node.set('target', '_top');
473 Y.all('form').each(function(node) {
474 node.set('target', '_top');
479 * Finds all nodes that match the given CSS selector and attaches events to them
480 * so that they toggle a given classname when clicked.
483 * @param {string} id An id containing elements to target
484 * @param {string} cssselector A selector to use to find targets
485 * @param {string} toggleclassname A classname to toggle
487 M.util.init_toggle_class_on_click = function(Y, id, cssselector, toggleclassname, togglecssselector) {
489 if (togglecssselector == '') {
490 togglecssselector = cssselector;
493 var node = Y.one('#'+id);
494 node.all(cssselector).each(function(n){
495 n.on('click', function(e){
497 if (e.target.test(cssselector) && !e.target.test('a') && !e.target.test('img')) {
498 if (this.test(togglecssselector)) {
499 this.toggleClass(toggleclassname);
501 this.ancestor(togglecssselector).toggleClass(toggleclassname);
506 // Attach this click event to the node rather than all selectors... will be much better
508 node.on('click', function(e){
509 if (e.target.hasClass('addtoall')) {
510 this.all(togglecssselector).addClass(toggleclassname);
511 } else if (e.target.hasClass('removefromall')) {
512 this.all(togglecssselector+'.'+toggleclassname).removeClass(toggleclassname);
518 * Initialises a colour picker
520 * Designed to be used with admin_setting_configcolourpicker although could be used
521 * anywhere, just give a text input an id and insert a div with the class admin_colourpicker
522 * above or below the input (must have the same parent) and then call this with the
525 * This code was mostly taken from my [Sam Hemelryk] css theme tool available in
526 * contrib/blocks. For better docs refer to that.
530 * @param {object} previewconf
532 M.util.init_colour_picker = function(Y, id, previewconf) {
534 * We need node and event-mouseenter
536 Y.use('node', 'event-mouseenter', function(){
538 * The colour picker object
547 eventMouseEnter : null,
548 eventMouseLeave : null,
549 eventMouseMove : null,
554 * Initalises the colour picker by putting everything together and wiring the events
557 this.input = Y.one('#'+id);
558 this.box = this.input.ancestor().one('.admin_colourpicker');
559 this.image = Y.Node.create('<img alt="" class="colourdialogue" />');
560 this.image.setAttribute('src', M.util.image_url('i/colourpicker', 'moodle'));
561 this.preview = Y.Node.create('<div class="previewcolour"></div>');
562 this.preview.setStyle('width', this.height/2).setStyle('height', this.height/2).setStyle('backgroundColor', this.input.get('value'));
563 this.current = Y.Node.create('<div class="currentcolour"></div>');
564 this.current.setStyle('width', this.height/2).setStyle('height', this.height/2 -1).setStyle('backgroundColor', this.input.get('value'));
565 this.box.setContent('').append(this.image).append(this.preview).append(this.current);
567 if (typeof(previewconf) === 'object' && previewconf !== null) {
568 Y.one('#'+id+'_preview').on('click', function(e){
569 if (Y.Lang.isString(previewconf.selector)) {
570 Y.all(previewconf.selector).setStyle(previewconf.style, this.input.get('value'));
572 for (var i in previewconf.selector) {
573 Y.all(previewconf.selector[i]).setStyle(previewconf.style, this.input.get('value'));
579 this.eventClick = this.image.on('click', this.pickColour, this);
580 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
583 * Starts to follow the mouse once it enter the image
585 startFollow : function(e) {
586 this.eventMouseEnter.detach();
587 this.eventMouseLeave = Y.on('mouseleave', this.endFollow, this.image, this);
588 this.eventMouseMove = this.image.on('mousemove', function(e){
589 this.preview.setStyle('backgroundColor', this.determineColour(e));
593 * Stops following the mouse
595 endFollow : function(e) {
596 this.eventMouseMove.detach();
597 this.eventMouseLeave.detach();
598 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
601 * Picks the colour the was clicked on
603 pickColour : function(e) {
604 var colour = this.determineColour(e);
605 this.input.set('value', colour);
606 this.current.setStyle('backgroundColor', colour);
609 * Calculates the colour fromthe given co-ordinates
611 determineColour : function(e) {
612 var eventx = Math.floor(e.pageX-e.target.getX());
613 var eventy = Math.floor(e.pageY-e.target.getY());
615 var imagewidth = this.width;
616 var imageheight = this.height;
617 var factor = this.factor;
618 var colour = [255,0,0];
629 var matrixcount = matrices.length;
630 var limit = Math.round(imagewidth/matrixcount);
631 var heightbreak = Math.round(imageheight/2);
633 for (var x = 0; x < imagewidth; x++) {
634 var divisor = Math.floor(x / limit);
635 var matrix = matrices[divisor];
637 colour[0] += matrix[0]*factor;
638 colour[1] += matrix[1]*factor;
639 colour[2] += matrix[2]*factor;
646 var pixel = [colour[0], colour[1], colour[2]];
647 if (eventy < heightbreak) {
648 pixel[0] += Math.floor(((255-pixel[0])/heightbreak) * (heightbreak - eventy));
649 pixel[1] += Math.floor(((255-pixel[1])/heightbreak) * (heightbreak - eventy));
650 pixel[2] += Math.floor(((255-pixel[2])/heightbreak) * (heightbreak - eventy));
651 } else if (eventy > heightbreak) {
652 pixel[0] = Math.floor((imageheight-eventy)*(pixel[0]/heightbreak));
653 pixel[1] = Math.floor((imageheight-eventy)*(pixel[1]/heightbreak));
654 pixel[2] = Math.floor((imageheight-eventy)*(pixel[2]/heightbreak));
657 return this.convert_rgb_to_hex(pixel);
660 * Converts an RGB value to Hex
662 convert_rgb_to_hex : function(rgb) {
664 var hexchars = "0123456789ABCDEF";
665 for (var i=0; i<3; i++) {
666 var number = Math.abs(rgb[i]);
667 if (number == 0 || isNaN(number)) {
670 hex += hexchars.charAt((number-number%16)/16)+hexchars.charAt(number%16);
677 * Initialise the colour picker :) Hoorah
683 M.util.init_block_hider = function(Y, config) {
684 Y.use('base', 'node', function(Y) {
685 M.util.block_hider = M.util.block_hider || (function(){
686 var blockhider = function() {
687 blockhider.superclass.constructor.apply(this, arguments);
689 blockhider.prototype = {
690 initializer : function(config) {
691 this.set('block', '#'+this.get('id'));
692 var b = this.get('block'),
695 if (t && (a = t.one('.block_action'))) {
696 var hide = Y.Node.create('<img class="block-hider-hide" tabindex="0" alt="'+config.tooltipVisible+'" title="'+config.tooltipVisible+'" />');
697 hide.setAttribute('src', this.get('iconVisible')).on('click', this.updateState, this, true);
698 hide.on('keypress', this.updateStateKey, this, true);
699 var show = Y.Node.create('<img class="block-hider-show" tabindex="0" alt="'+config.tooltipHidden+'" title="'+config.tooltipHidden+'" />');
700 show.setAttribute('src', this.get('iconHidden')).on('click', this.updateState, this, false);
701 show.on('keypress', this.updateStateKey, this, false);
702 a.insert(show, 0).insert(hide, 0);
705 updateState : function(e, hide) {
706 M.util.set_user_preference(this.get('preference'), hide);
708 this.get('block').addClass('hidden');
710 this.get('block').removeClass('hidden');
713 updateStateKey : function(e, hide) {
714 if (e.keyCode == 13) { //allow hide/show via enter key
715 this.updateState(this, hide);
719 Y.extend(blockhider, Y.Base, blockhider.prototype, {
725 value : M.util.image_url('t/switch_minus', 'moodle')
728 value : M.util.image_url('t/switch_plus', 'moodle')
731 setter : function(node) {
739 new M.util.block_hider(config);
744 * Returns a string registered in advance for usage in JavaScript
746 * If you do not pass the third parameter, the function will just return
747 * the corresponding value from the M.str object. If the third parameter is
748 * provided, the function performs {$a} placeholder substitution in the
749 * same way as PHP get_string() in Moodle does.
751 * @param {String} identifier string identifier
752 * @param {String} component the component providing the string
753 * @param {Object|String} a optional variable to populate placeholder with
755 M.util.get_string = function(identifier, component, a) {
758 if (M.cfg.developerdebug) {
759 // creating new instance if YUI is not optimal but it seems to be better way then
760 // require the instance via the function API - note that it is used in rare cases
761 // for debugging only anyway
762 // To ensure we don't kill browser performance if hundreds of get_string requests
763 // are made we cache the instance we generate within the M.util namespace.
764 // We don't publicly define the variable so that it doesn't get abused.
765 if (typeof M.util.get_string_yui_instance === 'undefined') {
766 M.util.get_string_yui_instance = new YUI({ debug : true });
768 var Y = M.util.get_string_yui_instance;
771 if (!M.str.hasOwnProperty(component) || !M.str[component].hasOwnProperty(identifier)) {
772 stringvalue = '[[' + identifier + ',' + component + ']]';
773 if (M.cfg.developerdebug) {
774 Y.log('undefined string ' + stringvalue, 'warn', 'M.util.get_string');
779 stringvalue = M.str[component][identifier];
781 if (typeof a == 'undefined') {
782 // no placeholder substitution requested
786 if (typeof a == 'number' || typeof a == 'string') {
787 // replace all occurrences of {$a} with the placeholder value
788 stringvalue = stringvalue.replace(/\{\$a\}/g, a);
792 if (typeof a == 'object') {
793 // replace {$a->key} placeholders
795 if (typeof a[key] != 'number' && typeof a[key] != 'string') {
796 if (M.cfg.developerdebug) {
797 Y.log('invalid value type for $a->' + key, 'warn', 'M.util.get_string');
801 var search = '{$a->' + key + '}';
802 search = search.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
803 search = new RegExp(search, 'g');
804 stringvalue = stringvalue.replace(search, a[key]);
809 if (M.cfg.developerdebug) {
810 Y.log('incorrect placeholder type', 'warn', 'M.util.get_string');
816 * Set focus on username or password field of the login form
818 M.util.focus_login_form = function(Y) {
819 var username = Y.one('#username');
820 var password = Y.one('#password');
822 if (username == null || password == null) {
823 // something is wrong here
827 var curElement = document.activeElement
828 if (curElement == 'undefined') {
829 // legacy browser - skip refocus protection
830 } else if (curElement.tagName == 'INPUT') {
831 // user was probably faster to focus something, do not mess with focus
835 if (username.get('value') == '') {
843 * Adds lightbox hidden element that covers the whole node.
846 * @param {Node} the node lightbox should be added to
847 * @retun {Node} created lightbox node
849 M.util.add_lightbox = function(Y, node) {
850 var WAITICON = {'pix':"i/loading_small",'component':'moodle'};
852 // Check if lightbox is already there
853 if (node.one('.lightbox')) {
854 return node.one('.lightbox');
857 node.setStyle('position', 'relative');
858 var waiticon = Y.Node.create('<img />')
860 'src' : M.util.image_url(WAITICON.pix, WAITICON.component)
863 'position' : 'relative',
867 var lightbox = Y.Node.create('<div></div>')
870 'position' : 'absolute',
875 'backgroundColor' : 'white',
876 'textAlign' : 'center'
878 .setAttribute('class', 'lightbox')
881 lightbox.appendChild(waiticon);
882 node.append(lightbox);
887 * Appends a hidden spinner element to the specified node.
890 * @param {Node} the node the spinner should be added to
891 * @return {Node} created spinner node
893 M.util.add_spinner = function(Y, node) {
894 var WAITICON = {'pix':"i/loading_small",'component':'moodle'};
896 // Check if spinner is already there
897 if (node.one('.spinner')) {
898 return node.one('.spinner');
901 var spinner = Y.Node.create('<img />')
902 .setAttribute('src', M.util.image_url(WAITICON.pix, WAITICON.component))
904 .addClass('iconsmall')
907 node.append(spinner);
911 //=== old legacy JS code, hopefully to be replaced soon by M.xx.yy and YUI3 code ===
913 function checkall() {
914 var inputs = document.getElementsByTagName('input');
915 for (var i = 0; i < inputs.length; i++) {
916 if (inputs[i].type == 'checkbox') {
917 if (inputs[i].disabled || inputs[i].readOnly) {
920 inputs[i].checked = true;
925 function checknone() {
926 var inputs = document.getElementsByTagName('input');
927 for (var i = 0; i < inputs.length; i++) {
928 if (inputs[i].type == 'checkbox') {
929 if (inputs[i].disabled || inputs[i].readOnly) {
932 inputs[i].checked = false;
938 * Either check, or uncheck, all checkboxes inside the element with id is
939 * @param id the id of the container
940 * @param checked the new state, either '' or 'checked'.
942 function select_all_in_element_with_id(id, checked) {
943 var container = document.getElementById(id);
947 var inputs = container.getElementsByTagName('input');
948 for (var i = 0; i < inputs.length; ++i) {
949 if (inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
950 inputs[i].checked = checked;
955 function select_all_in(elTagName, elClass, elId) {
956 var inputs = document.getElementsByTagName('input');
957 inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
958 for(var i = 0; i < inputs.length; ++i) {
959 if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
960 inputs[i].checked = 'checked';
965 function deselect_all_in(elTagName, elClass, elId) {
966 var inputs = document.getElementsByTagName('INPUT');
967 inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
968 for(var i = 0; i < inputs.length; ++i) {
969 if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
970 inputs[i].checked = '';
975 function confirm_if(expr, message) {
979 return confirm(message);
984 findParentNode (start, elementName, elementClass, elementID)
986 Travels up the DOM hierarchy to find a parent element with the
987 specified tag name, class, and id. All conditions must be met,
988 but any can be ommitted. Returns the BODY element if no match
991 function findParentNode(el, elName, elClass, elId) {
992 while (el.nodeName.toUpperCase() != 'BODY') {
993 if ((!elName || el.nodeName.toUpperCase() == elName) &&
994 (!elClass || el.className.indexOf(elClass) != -1) &&
995 (!elId || el.id == elId)) {
1003 findChildNode (start, elementName, elementClass, elementID)
1005 Travels down the DOM hierarchy to find all child elements with the
1006 specified tag name, class, and id. All conditions must be met,
1007 but any can be ommitted.
1008 Doesn't examine children of matches.
1010 function findChildNodes(start, tagName, elementClass, elementID, elementName) {
1011 var children = new Array();
1012 for (var i = 0; i < start.childNodes.length; i++) {
1013 var classfound = false;
1014 var child = start.childNodes[i];
1015 if((child.nodeType == 1) &&//element node type
1016 (elementClass && (typeof(child.className)=='string'))) {
1017 var childClasses = child.className.split(/\s+/);
1018 for (var childClassIndex in childClasses) {
1019 if (childClasses[childClassIndex]==elementClass) {
1025 if(child.nodeType == 1) { //element node type
1026 if ( (!tagName || child.nodeName == tagName) &&
1027 (!elementClass || classfound)&&
1028 (!elementID || child.id == elementID) &&
1029 (!elementName || child.name == elementName))
1031 children = children.concat(child);
1033 children = children.concat(findChildNodes(child, tagName, elementClass, elementID, elementName));
1040 function unmaskPassword(id) {
1041 var pw = document.getElementById(id);
1042 var chb = document.getElementById(id+'unmask');
1045 // first try IE way - it can not set name attribute later
1047 var newpw = document.createElement('<input type="text" autocomplete="off" name="'+pw.name+'">');
1049 var newpw = document.createElement('<input type="password" autocomplete="off" name="'+pw.name+'">');
1051 newpw.attributes['class'].nodeValue = pw.attributes['class'].nodeValue;
1053 var newpw = document.createElement('input');
1054 newpw.setAttribute('autocomplete', 'off');
1055 newpw.setAttribute('name', pw.name);
1057 newpw.setAttribute('type', 'text');
1059 newpw.setAttribute('type', 'password');
1061 newpw.setAttribute('class', pw.getAttribute('class'));
1064 newpw.size = pw.size;
1065 newpw.onblur = pw.onblur;
1066 newpw.onchange = pw.onchange;
1067 newpw.value = pw.value;
1068 pw.parentNode.replaceChild(newpw, pw);
1071 function filterByParent(elCollection, parentFinder) {
1072 var filteredCollection = [];
1073 for (var i = 0; i < elCollection.length; ++i) {
1074 var findParent = parentFinder(elCollection[i]);
1075 if (findParent.nodeName.toUpperCase() != 'BODY') {
1076 filteredCollection.push(elCollection[i]);
1079 return filteredCollection;
1083 All this is here just so that IE gets to handle oversized blocks
1084 in a visually pleasing manner. It does a browser detect. So sue me.
1087 function fix_column_widths() {
1088 var agt = navigator.userAgent.toLowerCase();
1089 if ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1)) {
1090 fix_column_width('left-column');
1091 fix_column_width('right-column');
1095 function fix_column_width(colName) {
1096 if(column = document.getElementById(colName)) {
1097 if(!column.offsetWidth) {
1098 setTimeout("fix_column_width('" + colName + "')", 20);
1103 var nodes = column.childNodes;
1105 for(i = 0; i < nodes.length; ++i) {
1106 if(nodes[i].className.indexOf("block") != -1 ) {
1107 if(width < nodes[i].offsetWidth) {
1108 width = nodes[i].offsetWidth;
1113 for(i = 0; i < nodes.length; ++i) {
1114 if(nodes[i].className.indexOf("block") != -1 ) {
1115 nodes[i].style.width = width + 'px';
1123 Insert myValue at current cursor position
1125 function insertAtCursor(myField, myValue) {
1127 if (document.selection) {
1129 sel = document.selection.createRange();
1132 // Mozilla/Netscape support
1133 else if (myField.selectionStart || myField.selectionStart == '0') {
1134 var startPos = myField.selectionStart;
1135 var endPos = myField.selectionEnd;
1136 myField.value = myField.value.substring(0, startPos)
1137 + myValue + myField.value.substring(endPos, myField.value.length);
1139 myField.value += myValue;
1145 Call instead of setting window.onload directly or setting body onload=.
1146 Adds your function to a chain of functions rather than overwriting anything
1149 function addonload(fn) {
1150 var oldhandler=window.onload;
1151 window.onload=function() {
1152 if(oldhandler) oldhandler();
1157 * Replacement for getElementsByClassName in browsers that aren't cool enough
1159 * Relying on the built-in getElementsByClassName is far, far faster than
1162 * Note: the third argument used to be an object with odd behaviour. It now
1163 * acts like the 'name' in the HTML5 spec, though the old behaviour is still
1164 * mimicked if you pass an object.
1166 * @param {Node} oElm The top-level node for searching. To search a whole
1167 * document, use `document`.
1168 * @param {String} strTagName filter by tag names
1169 * @param {String} name same as HTML5 spec
1171 function getElementsByClassName(oElm, strTagName, name) {
1172 // for backwards compatibility
1173 if(typeof name == "object") {
1174 var names = new Array();
1175 for(var i=0; i<name.length; i++) names.push(names[i]);
1176 name = names.join('');
1178 // use native implementation if possible
1179 if (oElm.getElementsByClassName && Array.filter) {
1180 if (strTagName == '*') {
1181 return oElm.getElementsByClassName(name);
1183 return Array.filter(oElm.getElementsByClassName(name), function(el) {
1184 return el.nodeName.toLowerCase() == strTagName.toLowerCase();
1188 // native implementation unavailable, fall back to slow method
1189 var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
1190 var arrReturnElements = new Array();
1191 var arrRegExpClassNames = new Array();
1192 var names = name.split(' ');
1193 for(var i=0; i<names.length; i++) {
1194 arrRegExpClassNames.push(new RegExp("(^|\\s)" + names[i].replace(/\-/g, "\\-") + "(\\s|$)"));
1198 for(var j=0; j<arrElements.length; j++) {
1199 oElement = arrElements[j];
1201 for(var k=0; k<arrRegExpClassNames.length; k++) {
1202 if(!arrRegExpClassNames[k].test(oElement.className)) {
1203 bMatchesAll = false;
1208 arrReturnElements.push(oElement);
1211 return (arrReturnElements)
1214 function openpopup(event, args) {
1217 if (event.preventDefault) {
1218 event.preventDefault();
1220 event.returnValue = false;
1224 // Make sure the name argument is set and valid.
1225 var nameregex = /[^a-z0-9_]/i;
1226 if (typeof args.name !== 'string') {
1227 args.name = '_blank';
1228 } else if (args.name.match(nameregex)) {
1229 // Cleans window name because IE does not support funky ones.
1230 args.name = args.name.replace(nameregex, '_');
1231 if (M.cfg.developerdebug) {
1232 alert('DEVELOPER NOTICE: Invalid \'name\' passed to openpopup()');
1236 var fullurl = args.url;
1237 if (!args.url.match(/https?:\/\//)) {
1238 fullurl = M.cfg.wwwroot + args.url;
1240 if (args.fullscreen) {
1241 args.options = args.options.
1242 replace(/top=\d+/, 'top=0').
1243 replace(/left=\d+/, 'left=0').
1244 replace(/width=\d+/, 'width=' + screen.availWidth).
1245 replace(/height=\d+/, 'height=' + screen.availHeight);
1247 var windowobj = window.open(fullurl,args.name,args.options);
1252 if (args.fullscreen) {
1253 // In some browser / OS combinations (E.g. Chrome on Windows), the
1254 // window initially opens slighly too big. The width and heigh options
1255 // seem to control the area inside the browser window, so what with
1256 // scroll-bars, etc. the actual window is bigger than the screen.
1257 // Therefore, we need to fix things up after the window is open.
1258 var hackcount = 100;
1259 var get_size_exactly_right = function() {
1260 windowobj.moveTo(0, 0);
1261 windowobj.resizeTo(screen.availWidth, screen.availHeight);
1263 // Unfortunately, it seems that in Chrome on Ubuntu, if you call
1264 // something like windowobj.resizeTo(1280, 1024) too soon (up to
1265 // about 50ms) after the window is open, then it actually behaves
1266 // as if you called windowobj.resizeTo(0, 0). Therefore, we need to
1267 // check that the resize actually worked, and if not, repeatedly try
1268 // again after a short delay until it works (but with a limit of
1269 // hackcount repeats.
1270 if (hackcount > 0 && (windowobj.innerHeight < 10 || windowobj.innerWidth < 10)) {
1272 setTimeout(get_size_exactly_right, 10);
1275 setTimeout(get_size_exactly_right, 0);
1282 /** Close the current browser window. */
1283 function close_window(e) {
1284 if (e.preventDefault) {
1287 e.returnValue = false;
1293 * Used in a couple of modules to hide navigation areas when using AJAX
1296 function show_item(itemid) {
1297 var item = document.getElementById(itemid);
1299 item.style.display = "";
1303 function destroy_item(itemid) {
1304 var item = document.getElementById(itemid);
1306 item.parentNode.removeChild(item);
1310 * Tranfer keyboard focus to the HTML element with the given id, if it exists.
1311 * @param controlid the control id.
1313 function focuscontrol(controlid) {
1314 var control = document.getElementById(controlid);
1321 * Transfers keyboard focus to an HTML element based on the old style style of focus
1322 * This function should be removed as soon as it is no longer used
1324 function old_onload_focus(formid, controlname) {
1325 if (document.forms[formid] && document.forms[formid].elements && document.forms[formid].elements[controlname]) {
1326 document.forms[formid].elements[controlname].focus();
1330 function build_querystring(obj) {
1331 return convert_object_to_string(obj, '&');
1334 function build_windowoptionsstring(obj) {
1335 return convert_object_to_string(obj, ',');
1338 function convert_object_to_string(obj, separator) {
1339 if (typeof obj !== 'object') {
1344 k = encodeURIComponent(k);
1346 if(obj[k] instanceof Array) {
1347 for(var i in value) {
1348 list.push(k+'[]='+encodeURIComponent(value[i]));
1351 list.push(k+'='+encodeURIComponent(value));
1354 return list.join(separator);
1357 function stripHTML(str) {
1358 var re = /<\S[^><]*>/g;
1359 var ret = str.replace(re, "");
1363 Number.prototype.fixed=function(n){
1365 return round(Number(this)*pow(10,n))/pow(10,n);
1367 function update_progress_bar (id, width, pt, msg, es){
1369 var status = document.getElementById("status_"+id);
1370 var percent_indicator = document.getElementById("pt_"+id);
1371 var progress_bar = document.getElementById("progress_"+id);
1372 var time_es = document.getElementById("time_"+id);
1373 status.innerHTML = msg;
1374 percent_indicator.innerHTML = percent.fixed(2) + '%';
1375 if(percent == 100) {
1376 progress_bar.style.background = "green";
1377 time_es.style.display = "none";
1379 progress_bar.style.background = "#FFCC66";
1381 time_es.innerHTML = "";
1383 time_es.innerHTML = es.fixed(2)+" sec";
1384 time_es.style.display
1388 progress_bar.style.width = width + "px";
1393 // ===== Deprecated core Javascript functions for Moodle ====
1394 // DO NOT USE!!!!!!!
1395 // Do not put this stuff in separate file because it only adds extra load on servers!
1398 * Used in a couple of modules to hide navigation areas when using AJAX
1400 function hide_item(itemid) {
1401 // use class='hiddenifjs' instead
1402 var item = document.getElementById(itemid);
1404 item.style.display = "none";
1408 M.util.help_popups = {
1409 setup : function(Y) {
1410 Y.one('body').delegate('click', this.open_popup, 'a.helplinkpopup', this);
1412 open_popup : function(e) {
1413 // Prevent the default page action
1416 // Grab the anchor that was clicked
1417 var anchor = e.target.ancestor('a', true);
1420 'url' : anchor.getAttribute('href'),
1438 args.options = options.join(',');
1444 M.util.help_icon = {
1447 add : function(Y, properties) {
1449 properties.node = Y.one('#'+properties.id);
1450 if (properties.node) {
1451 properties.node.on('click', this.display, this, properties);
1454 display : function(event, args) {
1455 event.preventDefault();
1456 if (M.util.help_icon.instance === null) {
1457 var Y = M.util.help_icon.Y;
1458 Y.use('overlay', 'io-base', 'event-mouseenter', 'node', 'event-key', 'escape', function(Y) {
1459 var help_content_overlay = {
1464 var strclose = Y.Escape.html(M.str.form.close);
1465 var footerbtn = Y.Node.create('<button class="closebtn">'+strclose+'</button>');
1466 // Create an overlay from markup
1467 this.overlay = new Y.Overlay({
1468 footerContent: footerbtn,
1475 this.overlay.render(Y.one(document.body));
1477 footerbtn.on('click', this.overlay.hide, this.overlay);
1479 var boundingBox = this.overlay.get("boundingBox");
1481 // Hide the menu if the user clicks outside of its content
1482 boundingBox.get("ownerDocument").on("mousedown", function (event) {
1483 var oTarget = event.target;
1484 var menuButton = Y.one("#"+args.id);
1486 if (!oTarget.compareTo(menuButton) &&
1487 !menuButton.contains(oTarget) &&
1488 !oTarget.compareTo(boundingBox) &&
1489 !boundingBox.contains(oTarget)) {
1490 this.overlay.hide();
1495 close : function(e) {
1497 this.helplink.focus();
1498 this.overlay.hide();
1501 display : function(event, args) {
1502 if (Y.one('html').get('dir') == 'rtl') {
1503 var overlayPosition = [Y.WidgetPositionAlign.TR, Y.WidgetPositionAlign.LC];
1505 var overlayPosition = [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.RC];
1508 this.helplink = args.node;
1510 this.overlay.set('bodyContent', Y.Node.create('<img src="'+M.cfg.loadingicon+'" class="spinner" />'));
1511 this.overlay.set("align", {node:args.node, points: overlayPosition});
1513 var fullurl = args.url;
1514 if (!args.url.match(/https?:\/\//)) {
1515 fullurl = M.cfg.wwwroot + args.url;
1518 var ajaxurl = fullurl + '&ajax=1';
1524 success: function(id, o, node) {
1525 this.display_callback(o.responseText);
1527 failure: function(id, o, node) {
1528 var debuginfo = o.statusText;
1529 if (M.cfg.developerdebug) {
1530 o.statusText += ' (' + ajaxurl + ')';
1532 this.display_callback('bodyContent',debuginfo);
1538 this.overlay.show();
1541 display_callback : function(content) {
1542 content = '<div role="alert">' + content + '</div>';
1543 this.overlay.set('bodyContent', content);
1546 hideContent : function() {
1548 help.overlay.hide();
1551 help_content_overlay.init();
1552 M.util.help_icon.instance = help_content_overlay;
1553 M.util.help_icon.instance.display(event, args);
1556 M.util.help_icon.instance.display(event, args);
1559 init : function(Y) {
1565 * Custom menu namespace
1567 M.core_custom_menu = {
1569 * This method is used to initialise a custom menu given the id that belongs
1570 * to the custom menu's root node.
1573 * @param {string} nodeid
1575 init : function(Y, nodeid) {
1576 var node = Y.one('#'+nodeid);
1578 Y.use('node-menunav', function(Y) {
1580 // Remove the javascript-disabled class.... obviously javascript is enabled.
1581 node.removeClass('javascript-disabled');
1582 // Initialise the menunav plugin
1583 node.plug(Y.Plugin.NodeMenuNav);
1590 * Used to store form manipulation methods and enhancments
1592 M.form = M.form || {};
1595 * Converts a nbsp indented select box into a multi drop down custom control much
1596 * like the custom menu. It also selectable categories on or off.
1598 * $form->init_javascript_enhancement('elementname','smartselect', array('selectablecategories'=>true|false, 'mode'=>'compact'|'spanning'));
1601 * @param {string} id
1602 * @param {Array} options
1604 M.form.init_smartselect = function(Y, id, options) {
1605 if (!id.match(/^id_/)) {
1608 var select = Y.one('select#'+id);
1612 Y.use('event-delegate',function(){
1618 currentvalue : null,
1622 selectablecategories : true,
1630 init : function(Y, id, args, nodes) {
1631 if (typeof(args)=='object') {
1632 for (var i in this.cfg) {
1633 if (args[i] || args[i]===false) {
1634 this.cfg[i] = args[i];
1639 // Display a loading message first up
1640 this.nodes.select = nodes.select;
1642 this.currentvalue = this.nodes.select.get('selectedIndex');
1643 this.currenttext = this.nodes.select.all('option').item(this.currentvalue).get('innerHTML');
1645 var options = Array();
1646 options[''] = {text:this.currenttext,value:'',depth:0,children:[]};
1647 this.nodes.select.all('option').each(function(option, index) {
1648 var rawtext = option.get('innerHTML');
1649 var text = rawtext.replace(/^( )*/, '');
1650 if (rawtext === text) {
1651 text = rawtext.replace(/^(\s)*/, '');
1652 var depth = (rawtext.length - text.length ) + 1;
1654 var depth = ((rawtext.length - text.length )/12)+1;
1656 option.set('innerHTML', text);
1657 options['i'+index] = {text:text,depth:depth,index:index,children:[]};
1660 this.structure = [];
1661 var structcount = 0;
1662 for (var i in options) {
1665 this.structure.push(o);
1669 var current = this.structure[structcount-1];
1670 for (var j = 0; j < o.depth-1;j++) {
1671 if (current && current.children) {
1672 current = current.children[current.children.length-1];
1675 if (current && current.children) {
1676 current.children.push(o);
1681 this.nodes.menu = Y.Node.create(this.generate_menu_content());
1682 this.nodes.menu.one('.smartselect_mask').setStyle('opacity', 0.01);
1683 this.nodes.menu.one('.smartselect_mask').setStyle('width', (this.nodes.select.get('offsetWidth')+5)+'px');
1684 this.nodes.menu.one('.smartselect_mask').setStyle('height', (this.nodes.select.get('offsetHeight'))+'px');
1686 if (this.cfg.mode == null) {
1687 var formwidth = this.nodes.select.ancestor('form').get('offsetWidth');
1688 if (formwidth < 400 || this.nodes.menu.get('offsetWidth') < formwidth*2) {
1689 this.cfg.mode = 'compact';
1691 this.cfg.mode = 'spanning';
1695 if (this.cfg.mode == 'compact') {
1696 this.nodes.menu.addClass('compactmenu');
1698 this.nodes.menu.addClass('spanningmenu');
1699 this.nodes.menu.delegate('mouseover', this.show_sub_menu, '.smartselect_submenuitem', this);
1702 Y.one(document.body).append(this.nodes.menu);
1703 var pos = this.nodes.select.getXY();
1705 this.nodes.menu.setXY(pos);
1706 this.nodes.menu.on('click', this.handle_click, this);
1708 Y.one(window).on('resize', function(){
1709 var pos = this.nodes.select.getXY();
1711 this.nodes.menu.setXY(pos);
1714 generate_menu_content : function() {
1715 var content = '<div id="'+this.id+'_smart_select" class="smartselect">';
1716 content += this.generate_submenu_content(this.structure[0], true);
1717 content += '</ul></div>';
1720 generate_submenu_content : function(item, rootelement) {
1721 this.submenucount++;
1723 if (item.children.length > 0) {
1725 content += '<div class="smartselect_mask" href="#ss_submenu'+this.submenucount+'"> </div>';
1726 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_menu">';
1727 content += '<div class="smartselect_menu_content">';
1729 content += '<li class="smartselect_submenuitem">';
1730 var categoryclass = (this.cfg.selectablecategories)?'selectable':'notselectable';
1731 content += '<a class="smartselect_menuitem_label '+categoryclass+'" href="#ss_submenu'+this.submenucount+'" value="'+item.index+'">'+item.text+'</a>';
1732 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_submenu">';
1733 content += '<div class="smartselect_submenu_content">';
1736 for (var i in item.children) {
1737 content += this.generate_submenu_content(item.children[i],false);
1740 content += '</div>';
1741 content += '</div>';
1747 content += '<li class="smartselect_menuitem">';
1748 content += '<a class="smartselect_menuitem_content selectable" href="#" value="'+item.index+'">'+item.text+'</a>';
1753 select : function(e) {
1756 this.currenttext = t.get('innerHTML');
1757 this.currentvalue = t.getAttribute('value');
1758 this.nodes.select.set('selectedIndex', this.currentvalue);
1761 handle_click : function(e) {
1762 var target = e.target;
1763 if (target.hasClass('smartselect_mask')) {
1765 } else if (target.hasClass('selectable') || target.hasClass('smartselect_menuitem')) {
1767 } else if (target.hasClass('smartselect_menuitem_label') || target.hasClass('smartselect_submenuitem')) {
1768 this.show_sub_menu(e);
1771 show_menu : function(e) {
1773 var menu = e.target.ancestor().one('.smartselect_menu');
1774 menu.addClass('visible');
1775 this.shownevent = Y.one(document.body).on('click', this.hide_menu, this);
1777 show_sub_menu : function(e) {
1779 var target = e.target;
1780 if (!target.hasClass('smartselect_submenuitem')) {
1781 target = target.ancestor('.smartselect_submenuitem');
1783 if (this.cfg.mode == 'compact' && target.one('.smartselect_submenu').hasClass('visible')) {
1784 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1787 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1788 target.one('.smartselect_submenu').addClass('visible');
1790 hide_menu : function() {
1791 this.nodes.menu.all('.visible').removeClass('visible');
1792 if (this.shownevent) {
1793 this.shownevent.detach();
1797 smartselect.init(Y, id, options, {select:select});
1801 /** List of flv players to be loaded */
1802 M.util.video_players = [];
1803 /** List of mp3 players to be loaded */
1804 M.util.audio_players = [];
1808 * @param id element id
1809 * @param fileurl media url
1812 * @param autosize true means detect size from media
1814 M.util.add_video_player = function (id, fileurl, width, height, autosize) {
1815 M.util.video_players.push({id: id, fileurl: fileurl, width: width, height: height, autosize: autosize, resized: false});
1824 M.util.add_audio_player = function (id, fileurl, small) {
1825 M.util.audio_players.push({id: id, fileurl: fileurl, small: small});
1829 * Initialise all audio and video player, must be called from page footer.
1831 M.util.load_flowplayer = function() {
1832 if (M.util.video_players.length == 0 && M.util.audio_players.length == 0) {
1835 if (typeof(flowplayer) == 'undefined') {
1838 var embed_function = function() {
1839 if (loaded || typeof(flowplayer) == 'undefined') {
1847 /* TODO: add CSS color overrides for the flv flow player */
1849 for(var i=0; i<M.util.video_players.length; i++) {
1850 var video = M.util.video_players[i];
1851 if (video.width > 0 && video.height > 0) {
1852 var src = {src: M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.14.swf', width: video.width, height: video.height};
1854 var src = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.14.swf';
1856 flowplayer(video.id, src, {
1857 plugins: {controls: controls},
1859 url: video.fileurl, autoPlay: false, autoBuffering: true, scaling: 'fit', mvideo: video,
1860 onMetaData: function(clip) {
1861 if (clip.mvideo.autosize && !clip.mvideo.resized) {
1862 clip.mvideo.resized = true;
1863 //alert("metadata!!! "+clip.width+' '+clip.height+' '+JSON.stringify(clip.metaData));
1864 if (typeof(clip.metaData.width) == 'undefined' || typeof(clip.metaData.height) == 'undefined') {
1865 // bad luck, we have to guess - we may not get metadata at all
1866 var width = clip.width;
1867 var height = clip.height;
1869 var width = clip.metaData.width;
1870 var height = clip.metaData.height;
1872 var minwidth = 300; // controls are messed up in smaller objects
1873 if (width < minwidth) {
1874 height = (height * minwidth) / width;
1878 var object = this._api();
1879 object.width = width;
1880 object.height = height;
1886 if (M.util.audio_players.length == 0) {
1899 backgroundGradient: [0.5,0,0.3]
1903 for (var j=0; j < document.styleSheets.length; j++) {
1905 // To avoid javascript security violation accessing cross domain stylesheets
1906 var allrules = false;
1908 if (typeof (document.styleSheets[j].rules) != 'undefined') {
1909 allrules = document.styleSheets[j].rules;
1910 } else if (typeof (document.styleSheets[j].cssRules) != 'undefined') {
1911 allrules = document.styleSheets[j].cssRules;
1920 // On cross domain style sheets Chrome V8 allows access to rules but returns null
1925 for(var i=0; i<allrules.length; i++) {
1927 if (/^\.mp3flowplayer_.*Color$/.test(allrules[i].selectorText)) {
1928 if (typeof(allrules[i].cssText) != 'undefined') {
1929 rule = allrules[i].cssText;
1930 } else if (typeof(allrules[i].style.cssText) != 'undefined') {
1931 rule = allrules[i].style.cssText;
1933 if (rule != '' && /.*color\s*:\s*([^;]+).*/gi.test(rule)) {
1934 rule = rule.replace(/.*color\s*:\s*([^;]+).*/gi, '$1');
1935 var colprop = allrules[i].selectorText.replace(/^\.mp3flowplayer_/, '');
1936 controls[colprop] = rule;
1943 for(i=0; i<M.util.audio_players.length; i++) {
1944 var audio = M.util.audio_players[i];
1946 controls.controlall = false;
1947 controls.height = 15;
1948 controls.time = false;
1950 controls.controlall = true;
1951 controls.height = 25;
1952 controls.time = true;
1954 flowplayer(audio.id, M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.14.swf', {
1955 plugins: {controls: controls, audio: {url: M.cfg.wwwroot + '/lib/flowplayer/flowplayer.audio-3.2.10.swf'}},
1956 clip: {url: audio.fileurl, provider: "audio", autoPlay: false}
1961 if (M.cfg.jsrev == -1) {
1962 var jsurl = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.11.js';
1964 var jsurl = M.cfg.wwwroot + '/lib/javascript.php?jsfile=/lib/flowplayer/flowplayer-3.2.11.min.js&rev=' + M.cfg.jsrev;
1966 var fileref = document.createElement('script');
1967 fileref.setAttribute('type','text/javascript');
1968 fileref.setAttribute('src', jsurl);
1969 fileref.onload = embed_function;
1970 fileref.onreadystatechange = embed_function;
1971 document.getElementsByTagName('head')[0].appendChild(fileref);