1 // Miscellaneous core Javascript functions for Moodle
2 // Global M object is initilised in inline javascript
5 * Add module to list of available modules that can be loaded from YUI.
6 * @param {Array} modules
8 M.yui.add_module = function(modules) {
9 for (var modname in modules) {
10 YUI_config.modules[modname] = modules[modname];
14 * The gallery version to use when loading YUI modules from the gallery.
15 * Will be changed every time when using local galleries.
17 M.yui.galleryversion = '2010.04.21-21-51';
20 * Various utility functions
22 M.util = M.util || {};
25 * Language strings - initialised from page footer.
30 * Returns url for images.
31 * @param {String} imagename
32 * @param {String} component
35 M.util.image_url = function(imagename, component) {
37 if (!component || component == '' || component == 'moodle' || component == 'core') {
41 var url = M.cfg.wwwroot + '/theme/image.php';
42 if (M.cfg.themerev > 0 && M.cfg.slasharguments == 1) {
43 if (!M.cfg.svgicons) {
46 url += '/' + M.cfg.theme + '/' + component + '/' + M.cfg.themerev + '/' + imagename;
48 url += '?theme=' + M.cfg.theme + '&component=' + component + '&rev=' + M.cfg.themerev + '&image=' + imagename;
49 if (!M.cfg.svgicons) {
57 M.util.in_array = function(item, array){
58 for( var i = 0; i<array.length; i++){
67 * Init a collapsible region, see print_collapsible_region in weblib.php
68 * @param {YUI} Y YUI3 instance with all libraries loaded
69 * @param {String} id the HTML id for the div.
70 * @param {String} userpref the user preference that records the state of this box. false if none.
71 * @param {String} strtooltip
73 M.util.init_collapsible_region = function(Y, id, userpref, strtooltip) {
74 Y.use('anim', function(Y) {
75 new M.util.CollapsibleRegion(Y, id, userpref, strtooltip);
80 * Object to handle a collapsible region : instantiate and forget styled object
84 * @param {YUI} Y YUI3 instance with all libraries loaded
85 * @param {String} id The HTML id for the div.
86 * @param {String} userpref The user preference that records the state of this box. false if none.
87 * @param {String} strtooltip
89 M.util.CollapsibleRegion = function(Y, id, userpref, strtooltip) {
90 // Record the pref name
91 this.userpref = userpref;
93 // Find the divs in the document.
94 this.div = Y.one('#'+id);
96 // Get the caption for the collapsible region
97 var caption = this.div.one('#'+id + '_caption');
100 var a = Y.Node.create('<a href="#"></a>');
101 a.setAttribute('title', strtooltip);
103 // Get all the nodes from caption, remove them and append them to <a>
104 while (caption.hasChildNodes()) {
105 child = caption.get('firstChild');
111 // Get the height of the div at this point before we shrink it if required
112 var height = this.div.get('offsetHeight');
113 var collapsedimage = 't/collapsed'; // ltr mode
114 if (right_to_left()) {
115 collapsedimage = 't/collapsed_rtl';
117 collapsedimage = 't/collapsed';
119 if (this.div.hasClass('collapsed')) {
120 // Add the correct image and record the YUI node created in the process
121 this.icon = Y.Node.create('<img src="'+M.util.image_url(collapsedimage, 'moodle')+'" alt="" />');
122 // Shrink the div as it is collapsed by default
123 this.div.setStyle('height', caption.get('offsetHeight')+'px');
125 // Add the correct image and record the YUI node created in the process
126 this.icon = Y.Node.create('<img src="'+M.util.image_url('t/expanded', 'moodle')+'" alt="" />');
130 // Create the animation.
131 var animation = new Y.Anim({
134 easing: Y.Easing.easeBoth,
135 to: {height:caption.get('offsetHeight')},
136 from: {height:height}
139 // Handler for the animation finishing.
140 animation.on('end', function() {
141 this.div.toggleClass('collapsed');
142 var collapsedimage = 't/collapsed'; // ltr mode
143 if (right_to_left()) {
144 collapsedimage = 't/collapsed_rtl';
146 collapsedimage = 't/collapsed';
148 if (this.div.hasClass('collapsed')) {
149 this.icon.set('src', M.util.image_url(collapsedimage, 'moodle'));
151 this.icon.set('src', M.util.image_url('t/expanded', 'moodle'));
155 // Hook up the event handler.
156 a.on('click', function(e, animation) {
158 // Animate to the appropriate size.
159 if (animation.get('running')) {
162 animation.set('reverse', this.div.hasClass('collapsed'));
163 // Update the user preference.
165 M.util.set_user_preference(this.userpref, !this.div.hasClass('collapsed'));
172 * The user preference that stores the state of this box.
176 M.util.CollapsibleRegion.prototype.userpref = null;
179 * The key divs that make up this
183 M.util.CollapsibleRegion.prototype.div = null;
186 * The key divs that make up this
190 M.util.CollapsibleRegion.prototype.icon = null;
193 * Makes a best effort to connect back to Moodle to update a user preference,
194 * however, there is no mechanism for finding out if the update succeeded.
196 * Before you can use this function in your JavsScript, you must have called
197 * user_preference_allow_ajax_update from moodlelib.php to tell Moodle that
198 * the udpate is allowed, and how to safely clean and submitted values.
200 * @param String name the name of the setting to udpate.
201 * @param String the value to set it to.
203 M.util.set_user_preference = function(name, value) {
204 YUI().use('io', function(Y) {
205 var url = M.cfg.wwwroot + '/lib/ajax/setuserpref.php?sesskey=' +
206 M.cfg.sesskey + '&pref=' + encodeURI(name) + '&value=' + encodeURI(value);
208 // If we are a developer, ensure that failures are reported.
213 if (M.cfg.developerdebug) {
214 cfg.on.failure = function(id, o, args) {
215 alert("Error updating user preference '" + name + "' using ajax. Clicking this link will repeat the Ajax call that failed so you can see the error: ");
225 * Prints a confirmation dialog in the style of DOM.confirm().
226 * @param object event A YUI DOM event or null if launched manually
227 * @param string message The message to show in the dialog
228 * @param string url The URL to forward to if YES is clicked. Disabled if fn is given
229 * @param function fn A JS function to run if YES is clicked.
231 M.util.show_confirm_dialog = function(e, args) {
232 var target = e.target;
233 if (e.preventDefault) {
237 YUI().use('yui2-container', 'yui2-event', function(Y) {
238 var simpledialog = new Y.YUI2.widget.SimpleDialog('confirmdialog',
247 simpledialog.setHeader(M.str.admin.confirmation);
248 simpledialog.setBody(args.message);
249 simpledialog.cfg.setProperty('icon', Y.YUI2.widget.SimpleDialog.ICON_WARN);
251 var handle_cancel = function() {
255 var handle_yes = function() {
259 // args comes from PHP, so callback will be a string, needs to be evaluated by JS
261 if (Y.Lang.isFunction(args.callback)) {
262 callback = args.callback;
264 callback = eval('('+args.callback+')');
267 if (Y.Lang.isObject(args.scope)) {
273 if (args.callbackargs) {
274 callback.apply(sc, args.callbackargs);
281 var targetancestor = null,
284 if (target.test('a')) {
285 window.location = target.get('href');
287 } else if ((targetancestor = target.ancestor('a')) !== null) {
288 window.location = targetancestor.get('href');
290 } else if (target.test('input')) {
291 targetform = target.ancestor(function(node) { return node.get('tagName').toLowerCase() == 'form'; });
292 // We cannot use target.ancestor('form') on the previous line
293 // because of http://yuilibrary.com/projects/yui3/ticket/2531561
297 if (target.get('name') && target.get('value')) {
298 targetform.append('<input type="hidden" name="' + target.get('name') +
299 '" value="' + target.get('value') + '">');
303 } else if (target.get('tagName').toLowerCase() == 'form') {
304 // We cannot use target.test('form') on the previous line because of
305 // http://yuilibrary.com/projects/yui3/ticket/2531561
308 } else if (M.cfg.developerdebug) {
309 alert("Element of type " + target.get('tagName') + " is not supported by the M.util.show_confirm_dialog function. Use A, INPUT, or FORM");
313 if (!args.cancellabel) {
314 args.cancellabel = M.str.moodle.cancel;
316 if (!args.continuelabel) {
317 args.continuelabel = M.str.moodle.yes;
321 {text: args.cancellabel, handler: handle_cancel, isDefault: true},
322 {text: args.continuelabel, handler: handle_yes}
325 simpledialog.cfg.queueProperty('buttons', buttons);
327 simpledialog.render(document.body);
332 /** Useful for full embedding of various stuff */
333 M.util.init_maximised_embed = function(Y, id) {
334 var obj = Y.one('#'+id);
339 var get_htmlelement_size = function(el, prop) {
340 if (Y.Lang.isString(el)) {
341 el = Y.one('#' + el);
343 // Ensure element exists.
345 var val = el.getStyle(prop);
347 val = el.getComputedStyle(prop);
349 return parseInt(val);
355 var resize_object = function() {
356 obj.setStyle('width', '0px');
357 obj.setStyle('height', '0px');
358 var newwidth = get_htmlelement_size('maincontent', 'width') - 35;
360 if (newwidth > 500) {
361 obj.setStyle('width', newwidth + 'px');
363 obj.setStyle('width', '500px');
366 var headerheight = get_htmlelement_size('page-header', 'height');
367 var footerheight = get_htmlelement_size('page-footer', 'height');
368 var newheight = parseInt(Y.one('body').get('winHeight')) - footerheight - headerheight - 100;
369 if (newheight < 400) {
372 obj.setStyle('height', newheight+'px');
376 // fix layout if window resized too
377 window.onresize = function() {
383 * Attach handler to single_select
385 * This code was deprecated in Moodle 2.4 and will be removed in Moodle 2.6
387 * Please see lib/yui/formautosubmit/formautosubmit.js for its replacement
389 M.util.init_select_autosubmit = function(Y, formid, selectid, nothing) {
390 if (M.cfg.developerdebug) {
391 Y.log("You are using a deprecated function call (M.util.init_select_autosubmit). Please look at rewriting your call to use moodle-core-formautosubmit");
393 Y.use('event-key', function() {
394 var select = Y.one('#'+selectid);
396 // Try to get the form by id
397 var form = Y.one('#'+formid) || (function(){
398 // Hmmm the form's id may have been overriden by an internal input
399 // with the name id which will KILL IE.
400 // We need to manually iterate at this point because if the case
401 // above is true YUI's ancestor method will also kill IE!
403 while (form && form.get('nodeName').toUpperCase() !== 'FORM') {
404 form = form.ancestor();
408 // Make sure we have the form
411 // Create a function to handle our change event
412 var processchange = function(e, paramobject) {
413 if ((nothing===false || select.get('value') != nothing) && paramobject.lastindex != select.get('selectedIndex')) {
414 // chrome doesn't pick up on a click when selecting an element in a select menu, so we use
415 // the on change event to fire this function. This just checks to see if a button was
416 // first pressed before redirecting to the appropriate page.
417 if (Y.UA.os == 'windows' && Y.UA.chrome){
418 if (buttonflag == 1) {
429 paramobject.lastindex = select.get('selectedIndex');
432 var changedown = function(e, paramobject) {
433 if ((nothing===false || select.get('value') != nothing) && paramobject.lastindex != select.get('selectedIndex')) {
434 if(e.keyCode == 13) {
437 paramobject.lastindex = select.get('selectedIndex');
441 var paramobject = new Object();
442 paramobject.lastindex = select.get('selectedIndex');
443 paramobject.eventchangeorblur = select.on('click', processchange, form, paramobject);
444 // Bad hack to circumvent problems with different browsers on different systems.
445 if (Y.UA.os == 'macintosh') {
447 paramobject.eventchangeorblur = select.on('change', processchange, form, paramobject);
449 paramobject.eventkeypress = Y.on('key', processchange, select, 'press:13', form, paramobject);
451 if(Y.UA.os == 'windows' && Y.UA.chrome) {
452 paramobject.eventchangeorblur = select.on('change', processchange, form, paramobject);
454 paramobject.eventkeypress = Y.on('keydown', changedown, select, '', form, paramobject);
462 * Attach handler to url_select
463 * Deprecated from 2.4 onwards.
464 * Please use @see init_select_autosubmit() for redirecting to a url (above).
465 * This function has accessability issues and also does not use the formid passed through as a parameter.
467 M.util.init_url_select = function(Y, formid, selectid, nothing) {
468 if (M.cfg.developerdebug) {
469 Y.log("You are using a deprecated function call (M.util.init_url_select). Please look at rewriting your call to use moodle-core-formautosubmit");
471 YUI().use('node', function(Y) {
472 Y.on('change', function() {
473 if ((nothing == false && Y.Lang.isBoolean(nothing)) || Y.one('#'+selectid).get('value') != nothing) {
474 window.location = M.cfg.wwwroot+Y.one('#'+selectid).get('value');
482 * Breaks out all links to the top frame - used in frametop page layout.
484 M.util.init_frametop = function(Y) {
485 Y.all('a').each(function(node) {
486 node.set('target', '_top');
488 Y.all('form').each(function(node) {
489 node.set('target', '_top');
494 * Finds all nodes that match the given CSS selector and attaches events to them
495 * so that they toggle a given classname when clicked.
498 * @param {string} id An id containing elements to target
499 * @param {string} cssselector A selector to use to find targets
500 * @param {string} toggleclassname A classname to toggle
502 M.util.init_toggle_class_on_click = function(Y, id, cssselector, toggleclassname, togglecssselector) {
504 if (togglecssselector == '') {
505 togglecssselector = cssselector;
508 var node = Y.one('#'+id);
509 node.all(cssselector).each(function(n){
510 n.on('click', function(e){
512 if (e.target.test(cssselector) && !e.target.test('a') && !e.target.test('img')) {
513 if (this.test(togglecssselector)) {
514 this.toggleClass(toggleclassname);
516 this.ancestor(togglecssselector).toggleClass(toggleclassname);
521 // Attach this click event to the node rather than all selectors... will be much better
523 node.on('click', function(e){
524 if (e.target.hasClass('addtoall')) {
525 this.all(togglecssselector).addClass(toggleclassname);
526 } else if (e.target.hasClass('removefromall')) {
527 this.all(togglecssselector+'.'+toggleclassname).removeClass(toggleclassname);
533 * Initialises a colour picker
535 * Designed to be used with admin_setting_configcolourpicker although could be used
536 * anywhere, just give a text input an id and insert a div with the class admin_colourpicker
537 * above or below the input (must have the same parent) and then call this with the
540 * This code was mostly taken from my [Sam Hemelryk] css theme tool available in
541 * contrib/blocks. For better docs refer to that.
545 * @param {object} previewconf
547 M.util.init_colour_picker = function(Y, id, previewconf) {
549 * We need node and event-mouseenter
551 Y.use('node', 'event-mouseenter', function(){
553 * The colour picker object
562 eventMouseEnter : null,
563 eventMouseLeave : null,
564 eventMouseMove : null,
569 * Initalises the colour picker by putting everything together and wiring the events
572 this.input = Y.one('#'+id);
573 this.box = this.input.ancestor().one('.admin_colourpicker');
574 this.image = Y.Node.create('<img alt="" class="colourdialogue" />');
575 this.image.setAttribute('src', M.util.image_url('i/colourpicker', 'moodle'));
576 this.preview = Y.Node.create('<div class="previewcolour"></div>');
577 this.preview.setStyle('width', this.height/2).setStyle('height', this.height/2).setStyle('backgroundColor', this.input.get('value'));
578 this.current = Y.Node.create('<div class="currentcolour"></div>');
579 this.current.setStyle('width', this.height/2).setStyle('height', this.height/2 -1).setStyle('backgroundColor', this.input.get('value'));
580 this.box.setContent('').append(this.image).append(this.preview).append(this.current);
582 if (typeof(previewconf) === 'object' && previewconf !== null) {
583 Y.one('#'+id+'_preview').on('click', function(e){
584 if (Y.Lang.isString(previewconf.selector)) {
585 Y.all(previewconf.selector).setStyle(previewconf.style, this.input.get('value'));
587 for (var i in previewconf.selector) {
588 Y.all(previewconf.selector[i]).setStyle(previewconf.style, this.input.get('value'));
594 this.eventClick = this.image.on('click', this.pickColour, this);
595 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
598 * Starts to follow the mouse once it enter the image
600 startFollow : function(e) {
601 this.eventMouseEnter.detach();
602 this.eventMouseLeave = Y.on('mouseleave', this.endFollow, this.image, this);
603 this.eventMouseMove = this.image.on('mousemove', function(e){
604 this.preview.setStyle('backgroundColor', this.determineColour(e));
608 * Stops following the mouse
610 endFollow : function(e) {
611 this.eventMouseMove.detach();
612 this.eventMouseLeave.detach();
613 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
616 * Picks the colour the was clicked on
618 pickColour : function(e) {
619 var colour = this.determineColour(e);
620 this.input.set('value', colour);
621 this.current.setStyle('backgroundColor', colour);
624 * Calculates the colour fromthe given co-ordinates
626 determineColour : function(e) {
627 var eventx = Math.floor(e.pageX-e.target.getX());
628 var eventy = Math.floor(e.pageY-e.target.getY());
630 var imagewidth = this.width;
631 var imageheight = this.height;
632 var factor = this.factor;
633 var colour = [255,0,0];
644 var matrixcount = matrices.length;
645 var limit = Math.round(imagewidth/matrixcount);
646 var heightbreak = Math.round(imageheight/2);
648 for (var x = 0; x < imagewidth; x++) {
649 var divisor = Math.floor(x / limit);
650 var matrix = matrices[divisor];
652 colour[0] += matrix[0]*factor;
653 colour[1] += matrix[1]*factor;
654 colour[2] += matrix[2]*factor;
661 var pixel = [colour[0], colour[1], colour[2]];
662 if (eventy < heightbreak) {
663 pixel[0] += Math.floor(((255-pixel[0])/heightbreak) * (heightbreak - eventy));
664 pixel[1] += Math.floor(((255-pixel[1])/heightbreak) * (heightbreak - eventy));
665 pixel[2] += Math.floor(((255-pixel[2])/heightbreak) * (heightbreak - eventy));
666 } else if (eventy > heightbreak) {
667 pixel[0] = Math.floor((imageheight-eventy)*(pixel[0]/heightbreak));
668 pixel[1] = Math.floor((imageheight-eventy)*(pixel[1]/heightbreak));
669 pixel[2] = Math.floor((imageheight-eventy)*(pixel[2]/heightbreak));
672 return this.convert_rgb_to_hex(pixel);
675 * Converts an RGB value to Hex
677 convert_rgb_to_hex : function(rgb) {
679 var hexchars = "0123456789ABCDEF";
680 for (var i=0; i<3; i++) {
681 var number = Math.abs(rgb[i]);
682 if (number == 0 || isNaN(number)) {
685 hex += hexchars.charAt((number-number%16)/16)+hexchars.charAt(number%16);
692 * Initialise the colour picker :) Hoorah
698 M.util.init_block_hider = function(Y, config) {
699 Y.use('base', 'node', function(Y) {
700 M.util.block_hider = M.util.block_hider || (function(){
701 var blockhider = function() {
702 blockhider.superclass.constructor.apply(this, arguments);
704 blockhider.prototype = {
705 initializer : function(config) {
706 this.set('block', '#'+this.get('id'));
707 var b = this.get('block'),
710 if (t && (a = t.one('.block_action'))) {
711 var hide = Y.Node.create('<img class="block-hider-hide" tabindex="0" alt="'+config.tooltipVisible+'" title="'+config.tooltipVisible+'" />');
712 hide.setAttribute('src', this.get('iconVisible')).on('click', this.updateState, this, true);
713 hide.on('keypress', this.updateStateKey, this, true);
714 var show = Y.Node.create('<img class="block-hider-show" tabindex="0" alt="'+config.tooltipHidden+'" title="'+config.tooltipHidden+'" />');
715 show.setAttribute('src', this.get('iconHidden')).on('click', this.updateState, this, false);
716 show.on('keypress', this.updateStateKey, this, false);
717 a.insert(show, 0).insert(hide, 0);
720 updateState : function(e, hide) {
721 M.util.set_user_preference(this.get('preference'), hide);
723 this.get('block').addClass('hidden');
725 this.get('block').removeClass('hidden');
728 updateStateKey : function(e, hide) {
729 if (e.keyCode == 13) { //allow hide/show via enter key
730 this.updateState(this, hide);
734 Y.extend(blockhider, Y.Base, blockhider.prototype, {
740 value : M.util.image_url('t/switch_minus', 'moodle')
743 value : M.util.image_url('t/switch_plus', 'moodle')
746 setter : function(node) {
754 new M.util.block_hider(config);
759 * @var pending_js - The keys are the list of all pending js actions.
762 M.util.pending_js = [];
763 M.util.complete_js = [];
766 * Register any long running javascript code with a unique identifier.
767 * Should be followed with a call to js_complete with a matching
768 * idenfitier when the code is complete. May also be called with no arguments
769 * to test if there is any js calls pending. This is relied on by behat so that
770 * it can wait for all pending updates before interacting with a page.
771 * @param String uniqid - optional, if provided,
772 * registers this identifier until js_complete is called.
773 * @return boolean - True if there is any pending js.
775 M.util.js_pending = function(uniqid) {
776 if (uniqid !== false) {
777 M.util.pending_js.push(uniqid);
780 return M.util.pending_js.length;
784 * Register listeners for Y.io start/end so we can wait for them in behat.
786 M.util.js_watch_io = function() {
787 YUI.add('moodle-core-io', function(Y) {
788 Y.on('io:start', function(id) {
789 M.util.js_pending('io:' + id);
791 Y.on('io:end', function(id) {
792 M.util.js_complete('io:' + id);
801 requires: ['moodle-core-io'],
809 M.util.js_pending('init');
810 M.util.js_watch_io();
813 * Unregister any long running javascript code by unique identifier.
814 * This function should form a matching pair with js_pending
816 * @param String uniqid - required, unregisters this identifier
817 * @return boolean - True if there is any pending js.
819 M.util.js_complete = function(uniqid) {
820 // Use the Y.Array.indexOf instead of the native because some older browsers do not support
821 // the native function. Y.Array polyfills the native function if it does not exist.
822 var index = Y.Array.indexOf(M.util.pending_js, uniqid);
824 M.util.complete_js.push(M.util.pending_js.splice(index, 1));
827 return M.util.pending_js.length;
831 * Returns a string registered in advance for usage in JavaScript
833 * If you do not pass the third parameter, the function will just return
834 * the corresponding value from the M.str object. If the third parameter is
835 * provided, the function performs {$a} placeholder substitution in the
836 * same way as PHP get_string() in Moodle does.
838 * @param {String} identifier string identifier
839 * @param {String} component the component providing the string
840 * @param {Object|String} a optional variable to populate placeholder with
842 M.util.get_string = function(identifier, component, a) {
845 if (M.cfg.developerdebug) {
846 // creating new instance if YUI is not optimal but it seems to be better way then
847 // require the instance via the function API - note that it is used in rare cases
848 // for debugging only anyway
849 // To ensure we don't kill browser performance if hundreds of get_string requests
850 // are made we cache the instance we generate within the M.util namespace.
851 // We don't publicly define the variable so that it doesn't get abused.
852 if (typeof M.util.get_string_yui_instance === 'undefined') {
853 M.util.get_string_yui_instance = new YUI({ debug : true });
855 var Y = M.util.get_string_yui_instance;
858 if (!M.str.hasOwnProperty(component) || !M.str[component].hasOwnProperty(identifier)) {
859 stringvalue = '[[' + identifier + ',' + component + ']]';
860 if (M.cfg.developerdebug) {
861 Y.log('undefined string ' + stringvalue, 'warn', 'M.util.get_string');
866 stringvalue = M.str[component][identifier];
868 if (typeof a == 'undefined') {
869 // no placeholder substitution requested
873 if (typeof a == 'number' || typeof a == 'string') {
874 // replace all occurrences of {$a} with the placeholder value
875 stringvalue = stringvalue.replace(/\{\$a\}/g, a);
879 if (typeof a == 'object') {
880 // replace {$a->key} placeholders
882 if (typeof a[key] != 'number' && typeof a[key] != 'string') {
883 if (M.cfg.developerdebug) {
884 Y.log('invalid value type for $a->' + key, 'warn', 'M.util.get_string');
888 var search = '{$a->' + key + '}';
889 search = search.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
890 search = new RegExp(search, 'g');
891 stringvalue = stringvalue.replace(search, a[key]);
896 if (M.cfg.developerdebug) {
897 Y.log('incorrect placeholder type', 'warn', 'M.util.get_string');
903 * Set focus on username or password field of the login form
905 M.util.focus_login_form = function(Y) {
906 var username = Y.one('#username');
907 var password = Y.one('#password');
909 if (username == null || password == null) {
910 // something is wrong here
914 var curElement = document.activeElement
915 if (curElement == 'undefined') {
916 // legacy browser - skip refocus protection
917 } else if (curElement.tagName == 'INPUT') {
918 // user was probably faster to focus something, do not mess with focus
922 if (username.get('value') == '') {
930 * Set focus on login error message
932 M.util.focus_login_error = function(Y) {
933 var errorlog = Y.one('#loginerrormessage');
940 * Adds lightbox hidden element that covers the whole node.
943 * @param {Node} the node lightbox should be added to
944 * @retun {Node} created lightbox node
946 M.util.add_lightbox = function(Y, node) {
947 var WAITICON = {'pix':"i/loading_small",'component':'moodle'};
949 // Check if lightbox is already there
950 if (node.one('.lightbox')) {
951 return node.one('.lightbox');
954 node.setStyle('position', 'relative');
955 var waiticon = Y.Node.create('<img />')
957 'src' : M.util.image_url(WAITICON.pix, WAITICON.component)
960 'position' : 'relative',
964 var lightbox = Y.Node.create('<div></div>')
967 'position' : 'absolute',
972 'backgroundColor' : 'white',
973 'textAlign' : 'center'
975 .setAttribute('class', 'lightbox')
978 lightbox.appendChild(waiticon);
979 node.append(lightbox);
984 * Appends a hidden spinner element to the specified node.
987 * @param {Node} the node the spinner should be added to
988 * @return {Node} created spinner node
990 M.util.add_spinner = function(Y, node) {
991 var WAITICON = {'pix':"i/loading_small",'component':'moodle'};
993 // Check if spinner is already there
994 if (node.one('.spinner')) {
995 return node.one('.spinner');
998 var spinner = Y.Node.create('<img />')
999 .setAttribute('src', M.util.image_url(WAITICON.pix, WAITICON.component))
1000 .addClass('spinner')
1001 .addClass('iconsmall')
1004 node.append(spinner);
1008 //=== old legacy JS code, hopefully to be replaced soon by M.xx.yy and YUI3 code ===
1010 function checkall() {
1011 var inputs = document.getElementsByTagName('input');
1012 for (var i = 0; i < inputs.length; i++) {
1013 if (inputs[i].type == 'checkbox') {
1014 if (inputs[i].disabled || inputs[i].readOnly) {
1017 inputs[i].checked = true;
1022 function checknone() {
1023 var inputs = document.getElementsByTagName('input');
1024 for (var i = 0; i < inputs.length; i++) {
1025 if (inputs[i].type == 'checkbox') {
1026 if (inputs[i].disabled || inputs[i].readOnly) {
1029 inputs[i].checked = false;
1035 * Either check, or uncheck, all checkboxes inside the element with id is
1036 * @param id the id of the container
1037 * @param checked the new state, either '' or 'checked'.
1039 function select_all_in_element_with_id(id, checked) {
1040 var container = document.getElementById(id);
1044 var inputs = container.getElementsByTagName('input');
1045 for (var i = 0; i < inputs.length; ++i) {
1046 if (inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
1047 inputs[i].checked = checked;
1052 function select_all_in(elTagName, elClass, elId) {
1053 var inputs = document.getElementsByTagName('input');
1054 inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
1055 for(var i = 0; i < inputs.length; ++i) {
1056 if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
1057 inputs[i].checked = 'checked';
1062 function deselect_all_in(elTagName, elClass, elId) {
1063 var inputs = document.getElementsByTagName('INPUT');
1064 inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
1065 for(var i = 0; i < inputs.length; ++i) {
1066 if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
1067 inputs[i].checked = '';
1072 function confirm_if(expr, message) {
1076 return confirm(message);
1081 findParentNode (start, elementName, elementClass, elementID)
1083 Travels up the DOM hierarchy to find a parent element with the
1084 specified tag name, class, and id. All conditions must be met,
1085 but any can be ommitted. Returns the BODY element if no match
1088 function findParentNode(el, elName, elClass, elId) {
1089 while (el.nodeName.toUpperCase() != 'BODY') {
1090 if ((!elName || el.nodeName.toUpperCase() == elName) &&
1091 (!elClass || el.className.indexOf(elClass) != -1) &&
1092 (!elId || el.id == elId)) {
1100 findChildNode (start, elementName, elementClass, elementID)
1102 Travels down the DOM hierarchy to find all child elements with the
1103 specified tag name, class, and id. All conditions must be met,
1104 but any can be ommitted.
1105 Doesn't examine children of matches.
1107 function findChildNodes(start, tagName, elementClass, elementID, elementName) {
1108 var children = new Array();
1109 for (var i = 0; i < start.childNodes.length; i++) {
1110 var classfound = false;
1111 var child = start.childNodes[i];
1112 if((child.nodeType == 1) &&//element node type
1113 (elementClass && (typeof(child.className)=='string'))) {
1114 var childClasses = child.className.split(/\s+/);
1115 for (var childClassIndex in childClasses) {
1116 if (childClasses[childClassIndex]==elementClass) {
1122 if(child.nodeType == 1) { //element node type
1123 if ( (!tagName || child.nodeName == tagName) &&
1124 (!elementClass || classfound)&&
1125 (!elementID || child.id == elementID) &&
1126 (!elementName || child.name == elementName))
1128 children = children.concat(child);
1130 children = children.concat(findChildNodes(child, tagName, elementClass, elementID, elementName));
1137 function unmaskPassword(id) {
1138 var pw = document.getElementById(id);
1139 var chb = document.getElementById(id+'unmask');
1141 // MDL-30438 - The capability to changing the value of input type is not supported by IE8 or lower.
1142 // Replacing existing child with a new one, removed all yui properties for the node. Therefore, this
1143 // functionality won't work in IE8 or lower.
1144 // This is a temporary fixed to allow other browsers to function properly.
1145 if (Y.UA.ie == 0 || Y.UA.ie >= 9) {
1149 pw.type = "password";
1151 } else { //IE Browser version 8 or lower
1153 // first try IE way - it can not set name attribute later
1155 var newpw = document.createElement('<input type="text" autocomplete="off" name="'+pw.name+'">');
1157 var newpw = document.createElement('<input type="password" autocomplete="off" name="'+pw.name+'">');
1159 newpw.attributes['class'].nodeValue = pw.attributes['class'].nodeValue;
1161 var newpw = document.createElement('input');
1162 newpw.setAttribute('autocomplete', 'off');
1163 newpw.setAttribute('name', pw.name);
1165 newpw.setAttribute('type', 'text');
1167 newpw.setAttribute('type', 'password');
1169 newpw.setAttribute('class', pw.getAttribute('class'));
1172 newpw.size = pw.size;
1173 newpw.onblur = pw.onblur;
1174 newpw.onchange = pw.onchange;
1175 newpw.value = pw.value;
1176 pw.parentNode.replaceChild(newpw, pw);
1180 function filterByParent(elCollection, parentFinder) {
1181 var filteredCollection = [];
1182 for (var i = 0; i < elCollection.length; ++i) {
1183 var findParent = parentFinder(elCollection[i]);
1184 if (findParent.nodeName.toUpperCase() != 'BODY') {
1185 filteredCollection.push(elCollection[i]);
1188 return filteredCollection;
1192 All this is here just so that IE gets to handle oversized blocks
1193 in a visually pleasing manner. It does a browser detect. So sue me.
1196 function fix_column_widths() {
1197 var agt = navigator.userAgent.toLowerCase();
1198 if ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1)) {
1199 fix_column_width('left-column');
1200 fix_column_width('right-column');
1204 function fix_column_width(colName) {
1205 if(column = document.getElementById(colName)) {
1206 if(!column.offsetWidth) {
1207 setTimeout("fix_column_width('" + colName + "')", 20);
1212 var nodes = column.childNodes;
1214 for(i = 0; i < nodes.length; ++i) {
1215 if(nodes[i].className.indexOf("block") != -1 ) {
1216 if(width < nodes[i].offsetWidth) {
1217 width = nodes[i].offsetWidth;
1222 for(i = 0; i < nodes.length; ++i) {
1223 if(nodes[i].className.indexOf("block") != -1 ) {
1224 nodes[i].style.width = width + 'px';
1232 Insert myValue at current cursor position
1234 function insertAtCursor(myField, myValue) {
1236 if (document.selection) {
1238 sel = document.selection.createRange();
1241 // Mozilla/Netscape support
1242 else if (myField.selectionStart || myField.selectionStart == '0') {
1243 var startPos = myField.selectionStart;
1244 var endPos = myField.selectionEnd;
1245 myField.value = myField.value.substring(0, startPos)
1246 + myValue + myField.value.substring(endPos, myField.value.length);
1248 myField.value += myValue;
1254 Call instead of setting window.onload directly or setting body onload=.
1255 Adds your function to a chain of functions rather than overwriting anything
1258 function addonload(fn) {
1259 var oldhandler=window.onload;
1260 window.onload=function() {
1261 if(oldhandler) oldhandler();
1266 * Replacement for getElementsByClassName in browsers that aren't cool enough
1268 * Relying on the built-in getElementsByClassName is far, far faster than
1271 * Note: the third argument used to be an object with odd behaviour. It now
1272 * acts like the 'name' in the HTML5 spec, though the old behaviour is still
1273 * mimicked if you pass an object.
1275 * @param {Node} oElm The top-level node for searching. To search a whole
1276 * document, use `document`.
1277 * @param {String} strTagName filter by tag names
1278 * @param {String} name same as HTML5 spec
1280 function getElementsByClassName(oElm, strTagName, name) {
1281 // for backwards compatibility
1282 if(typeof name == "object") {
1283 var names = new Array();
1284 for(var i=0; i<name.length; i++) names.push(names[i]);
1285 name = names.join('');
1287 // use native implementation if possible
1288 if (oElm.getElementsByClassName && Array.filter) {
1289 if (strTagName == '*') {
1290 return oElm.getElementsByClassName(name);
1292 return Array.filter(oElm.getElementsByClassName(name), function(el) {
1293 return el.nodeName.toLowerCase() == strTagName.toLowerCase();
1297 // native implementation unavailable, fall back to slow method
1298 var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
1299 var arrReturnElements = new Array();
1300 var arrRegExpClassNames = new Array();
1301 var names = name.split(' ');
1302 for(var i=0; i<names.length; i++) {
1303 arrRegExpClassNames.push(new RegExp("(^|\\s)" + names[i].replace(/\-/g, "\\-") + "(\\s|$)"));
1307 for(var j=0; j<arrElements.length; j++) {
1308 oElement = arrElements[j];
1310 for(var k=0; k<arrRegExpClassNames.length; k++) {
1311 if(!arrRegExpClassNames[k].test(oElement.className)) {
1312 bMatchesAll = false;
1317 arrReturnElements.push(oElement);
1320 return (arrReturnElements)
1324 * Increment a file name.
1326 * @param string file name.
1327 * @param boolean ignoreextension do not extract the extension prior to appending the
1328 * suffix. Useful when incrementing folder names.
1329 * @return string the incremented file name.
1331 function increment_filename(filename, ignoreextension) {
1333 var basename = filename;
1335 // Split the file name into the basename + extension.
1336 if (!ignoreextension) {
1337 var dotpos = filename.lastIndexOf('.');
1338 if (dotpos !== -1) {
1339 basename = filename.substr(0, dotpos);
1340 extension = filename.substr(dotpos, filename.length);
1344 // Look to see if the name already has (NN) at the end of it.
1346 var hasnumber = basename.match(/^(.*) \((\d+)\)$/);
1347 if (hasnumber !== null) {
1348 // Note the current number & remove it from the basename.
1349 number = parseInt(hasnumber[2], 10);
1350 basename = hasnumber[1];
1354 var newname = basename + ' (' + number + ')' + extension;
1359 * Return whether we are in right to left mode or not.
1363 function right_to_left() {
1364 var body = Y.one('body');
1366 if (body && body.hasClass('dir-rtl')) {
1372 function openpopup(event, args) {
1375 if (event.preventDefault) {
1376 event.preventDefault();
1378 event.returnValue = false;
1382 // Make sure the name argument is set and valid.
1383 var nameregex = /[^a-z0-9_]/i;
1384 if (typeof args.name !== 'string') {
1385 args.name = '_blank';
1386 } else if (args.name.match(nameregex)) {
1387 // Cleans window name because IE does not support funky ones.
1388 if (M.cfg.developerdebug) {
1389 alert('DEVELOPER NOTICE: Invalid \'name\' passed to openpopup(): ' + args.name);
1391 args.name = args.name.replace(nameregex, '_');
1394 var fullurl = args.url;
1395 if (!args.url.match(/https?:\/\//)) {
1396 fullurl = M.cfg.wwwroot + args.url;
1398 if (args.fullscreen) {
1399 args.options = args.options.
1400 replace(/top=\d+/, 'top=0').
1401 replace(/left=\d+/, 'left=0').
1402 replace(/width=\d+/, 'width=' + screen.availWidth).
1403 replace(/height=\d+/, 'height=' + screen.availHeight);
1405 var windowobj = window.open(fullurl,args.name,args.options);
1410 if (args.fullscreen) {
1411 // In some browser / OS combinations (E.g. Chrome on Windows), the
1412 // window initially opens slighly too big. The width and heigh options
1413 // seem to control the area inside the browser window, so what with
1414 // scroll-bars, etc. the actual window is bigger than the screen.
1415 // Therefore, we need to fix things up after the window is open.
1416 var hackcount = 100;
1417 var get_size_exactly_right = function() {
1418 windowobj.moveTo(0, 0);
1419 windowobj.resizeTo(screen.availWidth, screen.availHeight);
1421 // Unfortunately, it seems that in Chrome on Ubuntu, if you call
1422 // something like windowobj.resizeTo(1280, 1024) too soon (up to
1423 // about 50ms) after the window is open, then it actually behaves
1424 // as if you called windowobj.resizeTo(0, 0). Therefore, we need to
1425 // check that the resize actually worked, and if not, repeatedly try
1426 // again after a short delay until it works (but with a limit of
1427 // hackcount repeats.
1428 if (hackcount > 0 && (windowobj.innerHeight < 10 || windowobj.innerWidth < 10)) {
1430 setTimeout(get_size_exactly_right, 10);
1433 setTimeout(get_size_exactly_right, 0);
1440 /** Close the current browser window. */
1441 function close_window(e) {
1442 if (e.preventDefault) {
1445 e.returnValue = false;
1451 * Used in a couple of modules to hide navigation areas when using AJAX
1454 function show_item(itemid) {
1455 var item = document.getElementById(itemid);
1457 item.style.display = "";
1461 function destroy_item(itemid) {
1462 var item = document.getElementById(itemid);
1464 item.parentNode.removeChild(item);
1468 * Tranfer keyboard focus to the HTML element with the given id, if it exists.
1469 * @param controlid the control id.
1471 function focuscontrol(controlid) {
1472 var control = document.getElementById(controlid);
1479 * Transfers keyboard focus to an HTML element based on the old style style of focus
1480 * This function should be removed as soon as it is no longer used
1482 function old_onload_focus(formid, controlname) {
1483 if (document.forms[formid] && document.forms[formid].elements && document.forms[formid].elements[controlname]) {
1484 document.forms[formid].elements[controlname].focus();
1488 function build_querystring(obj) {
1489 return convert_object_to_string(obj, '&');
1492 function build_windowoptionsstring(obj) {
1493 return convert_object_to_string(obj, ',');
1496 function convert_object_to_string(obj, separator) {
1497 if (typeof obj !== 'object') {
1502 k = encodeURIComponent(k);
1504 if(obj[k] instanceof Array) {
1505 for(var i in value) {
1506 list.push(k+'[]='+encodeURIComponent(value[i]));
1509 list.push(k+'='+encodeURIComponent(value));
1512 return list.join(separator);
1515 function stripHTML(str) {
1516 var re = /<\S[^><]*>/g;
1517 var ret = str.replace(re, "");
1521 Number.prototype.fixed=function(n){
1523 return round(Number(this)*pow(10,n))/pow(10,n);
1525 function update_progress_bar (id, width, pt, msg, es){
1527 var status = document.getElementById("status_"+id);
1528 var percent_indicator = document.getElementById("pt_"+id);
1529 var progress_bar = document.getElementById("progress_"+id);
1530 var time_es = document.getElementById("time_"+id);
1531 status.innerHTML = msg;
1532 percent_indicator.innerHTML = percent.fixed(2) + '%';
1533 if(percent == 100) {
1534 progress_bar.style.background = "green";
1535 time_es.style.display = "none";
1537 progress_bar.style.background = "#FFCC66";
1539 time_es.innerHTML = "";
1541 time_es.innerHTML = es.fixed(2)+" sec";
1542 time_es.style.display
1546 progress_bar.style.width = width + "px";
1551 // ===== Deprecated core Javascript functions for Moodle ====
1552 // DO NOT USE!!!!!!!
1553 // Do not put this stuff in separate file because it only adds extra load on servers!
1556 * Used in a couple of modules to hide navigation areas when using AJAX
1558 function hide_item(itemid) {
1559 // use class='hiddenifjs' instead
1560 var item = document.getElementById(itemid);
1562 item.style.display = "none";
1566 M.util.help_popups = {
1567 setup : function(Y) {
1568 Y.one('body').delegate('click', this.open_popup, 'a.helplinkpopup', this);
1570 open_popup : function(e) {
1571 // Prevent the default page action
1574 // Grab the anchor that was clicked
1575 var anchor = e.target.ancestor('a', true);
1578 'url' : anchor.getAttribute('href'),
1596 args.options = options.join(',');
1603 * This code bas been deprecated and will be removed from Moodle 2.7
1605 * Please see lib/yui/popuphelp/popuphelp.js for its replacement
1607 M.util.help_icon = {
1608 initialised : false,
1609 setup : function(Y, properties) {
1610 this.add(Y, properties);
1613 if (M.cfg.developerdebug) {
1614 Y.log("You are using a deprecated function call (M.util.help_icon.add). " +
1615 "Please look at rewriting your call to support lib/yui/popuphelp/popuphelp.js");
1617 if (!this.initialised) {
1618 YUI().use('moodle-core-popuphelp', function() {
1619 M.core.init_popuphelp([]);
1622 this.initialised = true;
1627 * Custom menu namespace
1629 M.core_custom_menu = {
1631 * This method is used to initialise a custom menu given the id that belongs
1632 * to the custom menu's root node.
1635 * @param {string} nodeid
1637 init : function(Y, nodeid) {
1638 var node = Y.one('#'+nodeid);
1640 Y.use('node-menunav', function(Y) {
1642 // Remove the javascript-disabled class.... obviously javascript is enabled.
1643 node.removeClass('javascript-disabled');
1644 // Initialise the menunav plugin
1645 node.plug(Y.Plugin.NodeMenuNav);
1652 * Used to store form manipulation methods and enhancments
1654 M.form = M.form || {};
1657 * Converts a nbsp indented select box into a multi drop down custom control much
1658 * like the custom menu. It also selectable categories on or off.
1660 * $form->init_javascript_enhancement('elementname','smartselect', array('selectablecategories'=>true|false, 'mode'=>'compact'|'spanning'));
1663 * @param {string} id
1664 * @param {Array} options
1666 M.form.init_smartselect = function(Y, id, options) {
1667 if (!id.match(/^id_/)) {
1670 var select = Y.one('select#'+id);
1674 Y.use('event-delegate',function(){
1680 currentvalue : null,
1684 selectablecategories : true,
1692 init : function(Y, id, args, nodes) {
1693 if (typeof(args)=='object') {
1694 for (var i in this.cfg) {
1695 if (args[i] || args[i]===false) {
1696 this.cfg[i] = args[i];
1701 // Display a loading message first up
1702 this.nodes.select = nodes.select;
1704 this.currentvalue = this.nodes.select.get('selectedIndex');
1705 this.currenttext = this.nodes.select.all('option').item(this.currentvalue).get('innerHTML');
1707 var options = Array();
1708 options[''] = {text:this.currenttext,value:'',depth:0,children:[]};
1709 this.nodes.select.all('option').each(function(option, index) {
1710 var rawtext = option.get('innerHTML');
1711 var text = rawtext.replace(/^( )*/, '');
1712 if (rawtext === text) {
1713 text = rawtext.replace(/^(\s)*/, '');
1714 var depth = (rawtext.length - text.length ) + 1;
1716 var depth = ((rawtext.length - text.length )/12)+1;
1718 option.set('innerHTML', text);
1719 options['i'+index] = {text:text,depth:depth,index:index,children:[]};
1722 this.structure = [];
1723 var structcount = 0;
1724 for (var i in options) {
1727 this.structure.push(o);
1731 var current = this.structure[structcount-1];
1732 for (var j = 0; j < o.depth-1;j++) {
1733 if (current && current.children) {
1734 current = current.children[current.children.length-1];
1737 if (current && current.children) {
1738 current.children.push(o);
1743 this.nodes.menu = Y.Node.create(this.generate_menu_content());
1744 this.nodes.menu.one('.smartselect_mask').setStyle('opacity', 0.01);
1745 this.nodes.menu.one('.smartselect_mask').setStyle('width', (this.nodes.select.get('offsetWidth')+5)+'px');
1746 this.nodes.menu.one('.smartselect_mask').setStyle('height', (this.nodes.select.get('offsetHeight'))+'px');
1748 if (this.cfg.mode == null) {
1749 var formwidth = this.nodes.select.ancestor('form').get('offsetWidth');
1750 if (formwidth < 400 || this.nodes.menu.get('offsetWidth') < formwidth*2) {
1751 this.cfg.mode = 'compact';
1753 this.cfg.mode = 'spanning';
1757 if (this.cfg.mode == 'compact') {
1758 this.nodes.menu.addClass('compactmenu');
1760 this.nodes.menu.addClass('spanningmenu');
1761 this.nodes.menu.delegate('mouseover', this.show_sub_menu, '.smartselect_submenuitem', this);
1764 Y.one(document.body).append(this.nodes.menu);
1765 var pos = this.nodes.select.getXY();
1767 this.nodes.menu.setXY(pos);
1768 this.nodes.menu.on('click', this.handle_click, this);
1770 Y.one(window).on('resize', function(){
1771 var pos = this.nodes.select.getXY();
1773 this.nodes.menu.setXY(pos);
1776 generate_menu_content : function() {
1777 var content = '<div id="'+this.id+'_smart_select" class="smartselect">';
1778 content += this.generate_submenu_content(this.structure[0], true);
1779 content += '</ul></div>';
1782 generate_submenu_content : function(item, rootelement) {
1783 this.submenucount++;
1785 if (item.children.length > 0) {
1787 content += '<div class="smartselect_mask" href="#ss_submenu'+this.submenucount+'"> </div>';
1788 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_menu">';
1789 content += '<div class="smartselect_menu_content">';
1791 content += '<li class="smartselect_submenuitem">';
1792 var categoryclass = (this.cfg.selectablecategories)?'selectable':'notselectable';
1793 content += '<a class="smartselect_menuitem_label '+categoryclass+'" href="#ss_submenu'+this.submenucount+'" value="'+item.index+'">'+item.text+'</a>';
1794 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_submenu">';
1795 content += '<div class="smartselect_submenu_content">';
1798 for (var i in item.children) {
1799 content += this.generate_submenu_content(item.children[i],false);
1802 content += '</div>';
1803 content += '</div>';
1809 content += '<li class="smartselect_menuitem">';
1810 content += '<a class="smartselect_menuitem_content selectable" href="#" value="'+item.index+'">'+item.text+'</a>';
1815 select : function(e) {
1818 this.currenttext = t.get('innerHTML');
1819 this.currentvalue = t.getAttribute('value');
1820 this.nodes.select.set('selectedIndex', this.currentvalue);
1823 handle_click : function(e) {
1824 var target = e.target;
1825 if (target.hasClass('smartselect_mask')) {
1827 } else if (target.hasClass('selectable') || target.hasClass('smartselect_menuitem')) {
1829 } else if (target.hasClass('smartselect_menuitem_label') || target.hasClass('smartselect_submenuitem')) {
1830 this.show_sub_menu(e);
1833 show_menu : function(e) {
1835 var menu = e.target.ancestor().one('.smartselect_menu');
1836 menu.addClass('visible');
1837 this.shownevent = Y.one(document.body).on('click', this.hide_menu, this);
1839 show_sub_menu : function(e) {
1841 var target = e.target;
1842 if (!target.hasClass('smartselect_submenuitem')) {
1843 target = target.ancestor('.smartselect_submenuitem');
1845 if (this.cfg.mode == 'compact' && target.one('.smartselect_submenu').hasClass('visible')) {
1846 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1849 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1850 target.one('.smartselect_submenu').addClass('visible');
1852 hide_menu : function() {
1853 this.nodes.menu.all('.visible').removeClass('visible');
1854 if (this.shownevent) {
1855 this.shownevent.detach();
1859 smartselect.init(Y, id, options, {select:select});
1863 /** List of flv players to be loaded */
1864 M.util.video_players = [];
1865 /** List of mp3 players to be loaded */
1866 M.util.audio_players = [];
1870 * @param id element id
1871 * @param fileurl media url
1874 * @param autosize true means detect size from media
1876 M.util.add_video_player = function (id, fileurl, width, height, autosize) {
1877 M.util.video_players.push({id: id, fileurl: fileurl, width: width, height: height, autosize: autosize, resized: false});
1886 M.util.add_audio_player = function (id, fileurl, small) {
1887 M.util.audio_players.push({id: id, fileurl: fileurl, small: small});
1891 * Initialise all audio and video player, must be called from page footer.
1893 M.util.load_flowplayer = function() {
1894 if (M.util.video_players.length == 0 && M.util.audio_players.length == 0) {
1897 if (typeof(flowplayer) == 'undefined') {
1900 var embed_function = function() {
1901 if (loaded || typeof(flowplayer) == 'undefined') {
1909 /* TODO: add CSS color overrides for the flv flow player */
1911 for(var i=0; i<M.util.video_players.length; i++) {
1912 var video = M.util.video_players[i];
1913 if (video.width > 0 && video.height > 0) {
1914 var src = {src: M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.18.swf', width: video.width, height: video.height};
1916 var src = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.18.swf';
1918 flowplayer(video.id, src, {
1919 plugins: {controls: controls},
1921 url: video.fileurl, autoPlay: false, autoBuffering: true, scaling: 'fit', mvideo: video,
1922 onMetaData: function(clip) {
1923 if (clip.mvideo.autosize && !clip.mvideo.resized) {
1924 clip.mvideo.resized = true;
1925 //alert("metadata!!! "+clip.width+' '+clip.height+' '+JSON.stringify(clip.metaData));
1926 if (typeof(clip.metaData.width) == 'undefined' || typeof(clip.metaData.height) == 'undefined') {
1927 // bad luck, we have to guess - we may not get metadata at all
1928 var width = clip.width;
1929 var height = clip.height;
1931 var width = clip.metaData.width;
1932 var height = clip.metaData.height;
1934 var minwidth = 300; // controls are messed up in smaller objects
1935 if (width < minwidth) {
1936 height = (height * minwidth) / width;
1940 var object = this._api();
1941 object.width = width;
1942 object.height = height;
1948 if (M.util.audio_players.length == 0) {
1961 backgroundGradient: [0.5,0,0.3]
1965 for (var j=0; j < document.styleSheets.length; j++) {
1967 // To avoid javascript security violation accessing cross domain stylesheets
1968 var allrules = false;
1970 if (typeof (document.styleSheets[j].rules) != 'undefined') {
1971 allrules = document.styleSheets[j].rules;
1972 } else if (typeof (document.styleSheets[j].cssRules) != 'undefined') {
1973 allrules = document.styleSheets[j].cssRules;
1982 // On cross domain style sheets Chrome V8 allows access to rules but returns null
1987 for(var i=0; i<allrules.length; i++) {
1989 if (/^\.mp3flowplayer_.*Color$/.test(allrules[i].selectorText)) {
1990 if (typeof(allrules[i].cssText) != 'undefined') {
1991 rule = allrules[i].cssText;
1992 } else if (typeof(allrules[i].style.cssText) != 'undefined') {
1993 rule = allrules[i].style.cssText;
1995 if (rule != '' && /.*color\s*:\s*([^;]+).*/gi.test(rule)) {
1996 rule = rule.replace(/.*color\s*:\s*([^;]+).*/gi, '$1');
1997 var colprop = allrules[i].selectorText.replace(/^\.mp3flowplayer_/, '');
1998 controls[colprop] = rule;
2005 for(i=0; i<M.util.audio_players.length; i++) {
2006 var audio = M.util.audio_players[i];
2008 controls.controlall = false;
2009 controls.height = 15;
2010 controls.time = false;
2012 controls.controlall = true;
2013 controls.height = 25;
2014 controls.time = true;
2016 flowplayer(audio.id, M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.18.swf', {
2017 plugins: {controls: controls, audio: {url: M.cfg.wwwroot + '/lib/flowplayer/flowplayer.audio-3.2.11.swf'}},
2018 clip: {url: audio.fileurl, provider: "audio", autoPlay: false}
2023 if (M.cfg.jsrev == -1) {
2024 var jsurl = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.13.js';
2026 var jsurl = M.cfg.wwwroot + '/lib/javascript.php?jsfile=/lib/flowplayer/flowplayer-3.2.13.min.js&rev=' + M.cfg.jsrev;
2028 var fileref = document.createElement('script');
2029 fileref.setAttribute('type','text/javascript');
2030 fileref.setAttribute('src', jsurl);
2031 fileref.onload = embed_function;
2032 fileref.onreadystatechange = embed_function;
2033 document.getElementsByTagName('head')[0].appendChild(fileref);