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];
12 // Ensure thaat the YUI_config is applied to the main YUI instance.
13 Y.applyConfig(YUI_config);
16 * The gallery version to use when loading YUI modules from the gallery.
17 * Will be changed every time when using local galleries.
19 M.yui.galleryversion = '2010.04.21-21-51';
22 * Various utility functions
24 M.util = M.util || {};
27 * Language strings - initialised from page footer.
32 * Returns url for images.
33 * @param {String} imagename
34 * @param {String} component
37 M.util.image_url = function(imagename, component) {
39 if (!component || component == '' || component == 'moodle' || component == 'core') {
43 var url = M.cfg.wwwroot + '/theme/image.php';
44 if (M.cfg.themerev > 0 && M.cfg.slasharguments == 1) {
45 if (!M.cfg.svgicons) {
48 url += '/' + M.cfg.theme + '/' + component + '/' + M.cfg.themerev + '/' + imagename;
50 url += '?theme=' + M.cfg.theme + '&component=' + component + '&rev=' + M.cfg.themerev + '&image=' + imagename;
51 if (!M.cfg.svgicons) {
59 M.util.in_array = function(item, array){
60 for( var i = 0; i<array.length; i++){
69 * Init a collapsible region, see print_collapsible_region in weblib.php
70 * @param {YUI} Y YUI3 instance with all libraries loaded
71 * @param {String} id the HTML id for the div.
72 * @param {String} userpref the user preference that records the state of this box. false if none.
73 * @param {String} strtooltip
75 M.util.init_collapsible_region = function(Y, id, userpref, strtooltip) {
76 Y.use('anim', function(Y) {
77 new M.util.CollapsibleRegion(Y, id, userpref, strtooltip);
82 * Object to handle a collapsible region : instantiate and forget styled object
86 * @param {YUI} Y YUI3 instance with all libraries loaded
87 * @param {String} id The HTML id for the div.
88 * @param {String} userpref The user preference that records the state of this box. false if none.
89 * @param {String} strtooltip
91 M.util.CollapsibleRegion = function(Y, id, userpref, strtooltip) {
92 // Record the pref name
93 this.userpref = userpref;
95 // Find the divs in the document.
96 this.div = Y.one('#'+id);
98 // Get the caption for the collapsible region
99 var caption = this.div.one('#'+id + '_caption');
102 var a = Y.Node.create('<a href="#"></a>');
103 a.setAttribute('title', strtooltip);
105 // Get all the nodes from caption, remove them and append them to <a>
106 while (caption.hasChildNodes()) {
107 child = caption.get('firstChild');
113 // Get the height of the div at this point before we shrink it if required
114 var height = this.div.get('offsetHeight');
115 var collapsedimage = 't/collapsed'; // ltr mode
116 if (right_to_left()) {
117 collapsedimage = 't/collapsed_rtl';
119 collapsedimage = 't/collapsed';
121 if (this.div.hasClass('collapsed')) {
122 // Add the correct image and record the YUI node created in the process
123 this.icon = Y.Node.create('<img src="'+M.util.image_url(collapsedimage, 'moodle')+'" alt="" />');
124 // Shrink the div as it is collapsed by default
125 this.div.setStyle('height', caption.get('offsetHeight')+'px');
127 // Add the correct image and record the YUI node created in the process
128 this.icon = Y.Node.create('<img src="'+M.util.image_url('t/expanded', 'moodle')+'" alt="" />');
132 // Create the animation.
133 var animation = new Y.Anim({
136 easing: Y.Easing.easeBoth,
137 to: {height:caption.get('offsetHeight')},
138 from: {height:height}
141 // Handler for the animation finishing.
142 animation.on('end', function() {
143 this.div.toggleClass('collapsed');
144 var collapsedimage = 't/collapsed'; // ltr mode
145 if (right_to_left()) {
146 collapsedimage = 't/collapsed_rtl';
148 collapsedimage = 't/collapsed';
150 if (this.div.hasClass('collapsed')) {
151 this.icon.set('src', M.util.image_url(collapsedimage, 'moodle'));
153 this.icon.set('src', M.util.image_url('t/expanded', 'moodle'));
157 // Hook up the event handler.
158 a.on('click', function(e, animation) {
160 // Animate to the appropriate size.
161 if (animation.get('running')) {
164 animation.set('reverse', this.div.hasClass('collapsed'));
165 // Update the user preference.
167 M.util.set_user_preference(this.userpref, !this.div.hasClass('collapsed'));
174 * The user preference that stores the state of this box.
178 M.util.CollapsibleRegion.prototype.userpref = null;
181 * The key divs that make up this
185 M.util.CollapsibleRegion.prototype.div = null;
188 * The key divs that make up this
192 M.util.CollapsibleRegion.prototype.icon = null;
195 * Makes a best effort to connect back to Moodle to update a user preference,
196 * however, there is no mechanism for finding out if the update succeeded.
198 * Before you can use this function in your JavsScript, you must have called
199 * user_preference_allow_ajax_update from moodlelib.php to tell Moodle that
200 * the udpate is allowed, and how to safely clean and submitted values.
202 * @param String name the name of the setting to udpate.
203 * @param String the value to set it to.
205 M.util.set_user_preference = function(name, value) {
206 YUI().use('io', function(Y) {
207 var url = M.cfg.wwwroot + '/lib/ajax/setuserpref.php?sesskey=' +
208 M.cfg.sesskey + '&pref=' + encodeURI(name) + '&value=' + encodeURI(value);
210 // If we are a developer, ensure that failures are reported.
215 if (M.cfg.developerdebug) {
216 cfg.on.failure = function(id, o, args) {
217 alert("Error updating user preference '" + name + "' using ajax. Clicking this link will repeat the Ajax call that failed so you can see the error: ");
227 * Prints a confirmation dialog in the style of DOM.confirm().
229 * @method show_confirm_dialog
230 * @param {EventFacade} e
231 * @param {Object} args
232 * @param {String} args.message The question to ask the user
233 * @param {Function} [args.callback] A callback to apply on confirmation.
234 * @param {Object} [args.scope] The scope to use when calling the callback.
235 * @param {Object} [args.callbackargs] Any arguments to pass to the callback.
236 * @param {String} [args.cancellabel] The label to use on the cancel button.
237 * @param {String} [args.continuelabel] The label to use on the continue button.
239 M.util.show_confirm_dialog = function(e, args) {
240 var target = e.target;
241 if (e.preventDefault) {
245 YUI().use('moodle-core-notification-confirm', function(Y) {
246 var confirmationDialogue = new M.core.confirm({
252 title: M.util.get_string('confirmation', 'admin'),
253 noLabel: M.util.get_string('cancel', 'moodle'),
254 question: args.message
257 // The dialogue was submitted with a positive value indication.
258 confirmationDialogue.on('complete-yes', function(e) {
259 // Handle any callbacks.
261 if (!Y.Lang.isFunction(args.callback)) {
262 Y.log('Callbacks to show_confirm_dialog must now be functions. Please update your code to pass in a function instead.',
263 'warn', 'M.util.show_confirm_dialog');
267 var scope = e.target;
268 if (Y.Lang.isObject(args.scope)) {
272 var callbackargs = args.callbackargs || [];
273 args.callback.apply(scope, callbackargs);
277 var targetancestor = null,
280 if (target.test('a')) {
281 window.location = target.get('href');
283 } else if ((targetancestor = target.ancestor('a')) !== null) {
284 window.location = targetancestor.get('href');
286 } else if (target.test('input') || target.test('button')) {
287 targetform = target.ancestor('form', true);
291 if (target.get('name') && target.get('value')) {
292 targetform.append('<input type="hidden" name="' + target.get('name') +
293 '" value="' + target.get('value') + '">');
294 if (typeof M.core_formchangechecker !== 'undefined') {
295 M.core_formchangechecker.set_form_submitted();
300 } else if (target.test('form')) {
301 if (typeof M.core_formchangechecker !== 'undefined') {
302 M.core_formchangechecker.set_form_submitted();
307 Y.log("Element of type " + target.get('tagName') +
308 " is not supported by the M.util.show_confirm_dialog function. Use A, INPUT, BUTTON or FORM",
309 'warn', 'javascript-static');
313 if (args.cancellabel) {
314 confirmationDialogue.set('noLabel', args.cancellabel);
317 if (args.continuelabel) {
318 confirmationDialogue.set('yesLabel', args.continuelabel);
321 confirmationDialogue.render()
326 /** Useful for full embedding of various stuff */
327 M.util.init_maximised_embed = function(Y, id) {
328 var obj = Y.one('#'+id);
333 var get_htmlelement_size = function(el, prop) {
334 if (Y.Lang.isString(el)) {
335 el = Y.one('#' + el);
337 // Ensure element exists.
339 var val = el.getStyle(prop);
341 val = el.getComputedStyle(prop);
353 var resize_object = function() {
354 obj.setStyle('display', 'none');
355 var newwidth = get_htmlelement_size('maincontent', 'width') - 35;
357 if (newwidth > 500) {
358 obj.setStyle('width', newwidth + 'px');
360 obj.setStyle('width', '500px');
363 var headerheight = get_htmlelement_size('page-header', 'height');
364 var footerheight = get_htmlelement_size('page-footer', 'height');
365 var newheight = parseInt(Y.one('body').get('docHeight')) - footerheight - headerheight - 100;
366 if (newheight < 400) {
369 obj.setStyle('height', newheight+'px');
370 obj.setStyle('display', '');
374 // fix layout if window resized too
375 Y.use('event-resize', function (Y) {
376 Y.on("windowresize", function() {
383 * Breaks out all links to the top frame - used in frametop page layout.
385 M.util.init_frametop = function(Y) {
386 Y.all('a').each(function(node) {
387 node.set('target', '_top');
389 Y.all('form').each(function(node) {
390 node.set('target', '_top');
395 * Finds all nodes that match the given CSS selector and attaches events to them
396 * so that they toggle a given classname when clicked.
399 * @param {string} id An id containing elements to target
400 * @param {string} cssselector A selector to use to find targets
401 * @param {string} toggleclassname A classname to toggle
403 M.util.init_toggle_class_on_click = function(Y, id, cssselector, toggleclassname, togglecssselector) {
405 if (togglecssselector == '') {
406 togglecssselector = cssselector;
409 var node = Y.one('#'+id);
410 node.all(cssselector).each(function(n){
411 n.on('click', function(e){
413 if (e.target.test(cssselector) && !e.target.test('a') && !e.target.test('img')) {
414 if (this.test(togglecssselector)) {
415 this.toggleClass(toggleclassname);
417 this.ancestor(togglecssselector).toggleClass(toggleclassname);
422 // Attach this click event to the node rather than all selectors... will be much better
424 node.on('click', function(e){
425 if (e.target.hasClass('addtoall')) {
426 this.all(togglecssselector).addClass(toggleclassname);
427 } else if (e.target.hasClass('removefromall')) {
428 this.all(togglecssselector+'.'+toggleclassname).removeClass(toggleclassname);
434 * Initialises a colour picker
436 * Designed to be used with admin_setting_configcolourpicker although could be used
437 * anywhere, just give a text input an id and insert a div with the class admin_colourpicker
438 * above or below the input (must have the same parent) and then call this with the
441 * This code was mostly taken from my [Sam Hemelryk] css theme tool available in
442 * contrib/blocks. For better docs refer to that.
446 * @param {object} previewconf
448 M.util.init_colour_picker = function(Y, id, previewconf) {
450 * We need node and event-mouseenter
452 Y.use('node', 'event-mouseenter', function(){
454 * The colour picker object
463 eventMouseEnter : null,
464 eventMouseLeave : null,
465 eventMouseMove : null,
470 * Initalises the colour picker by putting everything together and wiring the events
473 this.input = Y.one('#'+id);
474 this.box = this.input.ancestor().one('.admin_colourpicker');
475 this.image = Y.Node.create('<img alt="" class="colourdialogue" />');
476 this.image.setAttribute('src', M.util.image_url('i/colourpicker', 'moodle'));
477 this.preview = Y.Node.create('<div class="previewcolour"></div>');
478 this.preview.setStyle('width', this.height/2).setStyle('height', this.height/2).setStyle('backgroundColor', this.input.get('value'));
479 this.current = Y.Node.create('<div class="currentcolour"></div>');
480 this.current.setStyle('width', this.height/2).setStyle('height', this.height/2 -1).setStyle('backgroundColor', this.input.get('value'));
481 this.box.setContent('').append(this.image).append(this.preview).append(this.current);
483 if (typeof(previewconf) === 'object' && previewconf !== null) {
484 Y.one('#'+id+'_preview').on('click', function(e){
485 if (Y.Lang.isString(previewconf.selector)) {
486 Y.all(previewconf.selector).setStyle(previewconf.style, this.input.get('value'));
488 for (var i in previewconf.selector) {
489 Y.all(previewconf.selector[i]).setStyle(previewconf.style, this.input.get('value'));
495 this.eventClick = this.image.on('click', this.pickColour, this);
496 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
499 * Starts to follow the mouse once it enter the image
501 startFollow : function(e) {
502 this.eventMouseEnter.detach();
503 this.eventMouseLeave = Y.on('mouseleave', this.endFollow, this.image, this);
504 this.eventMouseMove = this.image.on('mousemove', function(e){
505 this.preview.setStyle('backgroundColor', this.determineColour(e));
509 * Stops following the mouse
511 endFollow : function(e) {
512 this.eventMouseMove.detach();
513 this.eventMouseLeave.detach();
514 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
517 * Picks the colour the was clicked on
519 pickColour : function(e) {
520 var colour = this.determineColour(e);
521 this.input.set('value', colour);
522 this.current.setStyle('backgroundColor', colour);
525 * Calculates the colour fromthe given co-ordinates
527 determineColour : function(e) {
528 var eventx = Math.floor(e.pageX-e.target.getX());
529 var eventy = Math.floor(e.pageY-e.target.getY());
531 var imagewidth = this.width;
532 var imageheight = this.height;
533 var factor = this.factor;
534 var colour = [255,0,0];
545 var matrixcount = matrices.length;
546 var limit = Math.round(imagewidth/matrixcount);
547 var heightbreak = Math.round(imageheight/2);
549 for (var x = 0; x < imagewidth; x++) {
550 var divisor = Math.floor(x / limit);
551 var matrix = matrices[divisor];
553 colour[0] += matrix[0]*factor;
554 colour[1] += matrix[1]*factor;
555 colour[2] += matrix[2]*factor;
562 var pixel = [colour[0], colour[1], colour[2]];
563 if (eventy < heightbreak) {
564 pixel[0] += Math.floor(((255-pixel[0])/heightbreak) * (heightbreak - eventy));
565 pixel[1] += Math.floor(((255-pixel[1])/heightbreak) * (heightbreak - eventy));
566 pixel[2] += Math.floor(((255-pixel[2])/heightbreak) * (heightbreak - eventy));
567 } else if (eventy > heightbreak) {
568 pixel[0] = Math.floor((imageheight-eventy)*(pixel[0]/heightbreak));
569 pixel[1] = Math.floor((imageheight-eventy)*(pixel[1]/heightbreak));
570 pixel[2] = Math.floor((imageheight-eventy)*(pixel[2]/heightbreak));
573 return this.convert_rgb_to_hex(pixel);
576 * Converts an RGB value to Hex
578 convert_rgb_to_hex : function(rgb) {
580 var hexchars = "0123456789ABCDEF";
581 for (var i=0; i<3; i++) {
582 var number = Math.abs(rgb[i]);
583 if (number == 0 || isNaN(number)) {
586 hex += hexchars.charAt((number-number%16)/16)+hexchars.charAt(number%16);
593 * Initialise the colour picker :) Hoorah
599 M.util.init_block_hider = function(Y, config) {
600 Y.use('base', 'node', function(Y) {
601 M.util.block_hider = M.util.block_hider || (function(){
602 var blockhider = function() {
603 blockhider.superclass.constructor.apply(this, arguments);
605 blockhider.prototype = {
606 initializer : function(config) {
607 this.set('block', '#'+this.get('id'));
608 var b = this.get('block'),
613 if (t && (a = t.one('.block_action'))) {
614 hide = Y.Node.create('<img />')
615 .addClass('block-hider-hide')
617 alt: config.tooltipVisible,
618 src: this.get('iconVisible'),
620 'title': config.tooltipVisible
622 hide.on('keypress', this.updateStateKey, this, true);
623 hide.on('click', this.updateState, this, true);
625 show = Y.Node.create('<img />')
626 .addClass('block-hider-show')
628 alt: config.tooltipHidden,
629 src: this.get('iconHidden'),
631 'title': config.tooltipHidden
633 show.on('keypress', this.updateStateKey, this, false);
634 show.on('click', this.updateState, this, false);
636 a.insert(show, 0).insert(hide, 0);
639 updateState : function(e, hide) {
640 M.util.set_user_preference(this.get('preference'), hide);
642 this.get('block').addClass('hidden');
643 this.get('block').one('.block-hider-show').focus();
645 this.get('block').removeClass('hidden');
646 this.get('block').one('.block-hider-hide').focus();
649 updateStateKey : function(e, hide) {
650 if (e.keyCode == 13) { //allow hide/show via enter key
651 this.updateState(this, hide);
655 Y.extend(blockhider, Y.Base, blockhider.prototype, {
661 value : M.util.image_url('t/switch_minus', 'moodle')
664 value : M.util.image_url('t/switch_plus', 'moodle')
667 setter : function(node) {
675 new M.util.block_hider(config);
680 * @var pending_js - The keys are the list of all pending js actions.
683 M.util.pending_js = [];
684 M.util.complete_js = [];
687 * Register any long running javascript code with a unique identifier.
688 * Should be followed with a call to js_complete with a matching
689 * idenfitier when the code is complete. May also be called with no arguments
690 * to test if there is any js calls pending. This is relied on by behat so that
691 * it can wait for all pending updates before interacting with a page.
692 * @param String uniqid - optional, if provided,
693 * registers this identifier until js_complete is called.
694 * @return boolean - True if there is any pending js.
696 M.util.js_pending = function(uniqid) {
697 if (uniqid !== false) {
698 M.util.pending_js.push(uniqid);
701 return M.util.pending_js.length;
705 M.util.js_pending('init');
708 * Register listeners for Y.io start/end so we can wait for them in behat.
710 YUI.add('moodle-core-io', function(Y) {
711 Y.on('io:start', function(id) {
712 M.util.js_pending('io:' + id);
714 Y.on('io:end', function(id) {
715 M.util.js_complete('io:' + id);
725 * Unregister any long running javascript code by unique identifier.
726 * This function should form a matching pair with js_pending
728 * @param String uniqid - required, unregisters this identifier
729 * @return boolean - True if there is any pending js.
731 M.util.js_complete = function(uniqid) {
732 // Use the Y.Array.indexOf instead of the native because some older browsers do not support
733 // the native function. Y.Array polyfills the native function if it does not exist.
734 var index = Y.Array.indexOf(M.util.pending_js, uniqid);
736 M.util.complete_js.push(M.util.pending_js.splice(index, 1));
739 return M.util.pending_js.length;
743 * Returns a string registered in advance for usage in JavaScript
745 * If you do not pass the third parameter, the function will just return
746 * the corresponding value from the M.str object. If the third parameter is
747 * provided, the function performs {$a} placeholder substitution in the
748 * same way as PHP get_string() in Moodle does.
750 * @param {String} identifier string identifier
751 * @param {String} component the component providing the string
752 * @param {Object|String} a optional variable to populate placeholder with
754 M.util.get_string = function(identifier, component, a) {
757 if (M.cfg.developerdebug) {
758 // creating new instance if YUI is not optimal but it seems to be better way then
759 // require the instance via the function API - note that it is used in rare cases
760 // for debugging only anyway
761 // To ensure we don't kill browser performance if hundreds of get_string requests
762 // are made we cache the instance we generate within the M.util namespace.
763 // We don't publicly define the variable so that it doesn't get abused.
764 if (typeof M.util.get_string_yui_instance === 'undefined') {
765 M.util.get_string_yui_instance = new YUI({ debug : true });
767 var Y = M.util.get_string_yui_instance;
770 if (!M.str.hasOwnProperty(component) || !M.str[component].hasOwnProperty(identifier)) {
771 stringvalue = '[[' + identifier + ',' + component + ']]';
772 if (M.cfg.developerdebug) {
773 Y.log('undefined string ' + stringvalue, 'warn', 'M.util.get_string');
778 stringvalue = M.str[component][identifier];
780 if (typeof a == 'undefined') {
781 // no placeholder substitution requested
785 if (typeof a == 'number' || typeof a == 'string') {
786 // replace all occurrences of {$a} with the placeholder value
787 stringvalue = stringvalue.replace(/\{\$a\}/g, a);
791 if (typeof a == 'object') {
792 // replace {$a->key} placeholders
794 if (typeof a[key] != 'number' && typeof a[key] != 'string') {
795 if (M.cfg.developerdebug) {
796 Y.log('invalid value type for $a->' + key, 'warn', 'M.util.get_string');
800 var search = '{$a->' + key + '}';
801 search = search.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
802 search = new RegExp(search, 'g');
803 stringvalue = stringvalue.replace(search, a[key]);
808 if (M.cfg.developerdebug) {
809 Y.log('incorrect placeholder type', 'warn', 'M.util.get_string');
815 * Set focus on username or password field of the login form.
816 * @deprecated since Moodle 3.3.
818 M.util.focus_login_form = function(Y) {
819 Y.log('M.util.focus_login_form no longer does anything. Please use jquery instead.', 'warn', 'javascript-static.js');
823 * Set focus on login error message.
824 * @deprecated since Moodle 3.3.
826 M.util.focus_login_error = function(Y) {
827 Y.log('M.util.focus_login_error no longer does anything. Please use jquery instead.', 'warn', 'javascript-static.js');
831 * Adds lightbox hidden element that covers the whole node.
834 * @param {Node} the node lightbox should be added to
835 * @retun {Node} created lightbox node
837 M.util.add_lightbox = function(Y, node) {
838 var WAITICON = {'pix':"i/loading_small",'component':'moodle'};
840 // Check if lightbox is already there
841 if (node.one('.lightbox')) {
842 return node.one('.lightbox');
845 node.setStyle('position', 'relative');
846 var waiticon = Y.Node.create('<img />')
848 'src' : M.util.image_url(WAITICON.pix, WAITICON.component)
851 'position' : 'relative',
855 var lightbox = Y.Node.create('<div></div>')
858 'position' : 'absolute',
863 'backgroundColor' : 'white',
864 'textAlign' : 'center'
866 .setAttribute('class', 'lightbox')
869 lightbox.appendChild(waiticon);
870 node.append(lightbox);
875 * Appends a hidden spinner element to the specified node.
878 * @param {Node} the node the spinner should be added to
879 * @return {Node} created spinner node
881 M.util.add_spinner = function(Y, node) {
882 var WAITICON = {'pix':"i/loading_small",'component':'moodle'};
884 // Check if spinner is already there
885 if (node.one('.spinner')) {
886 return node.one('.spinner');
889 var spinner = Y.Node.create('<img />')
890 .setAttribute('src', M.util.image_url(WAITICON.pix, WAITICON.component))
892 .addClass('iconsmall')
895 node.append(spinner);
900 * @deprecated since Moodle 3.3.
902 function checkall() {
903 throw new Error('checkall can not be used any more. Please use jQuery instead.');
907 * @deprecated since Moodle 3.3.
909 function checknone() {
910 throw new Error('checknone can not be used any more. Please use jQuery instead.');
914 * @deprecated since Moodle 3.3.
916 function select_all_in_element_with_id(id, checked) {
917 throw new Error('select_all_in_element_with_id can not be used any more. Please use jQuery instead.');
921 * @deprecated since Moodle 3.3.
923 function select_all_in(elTagName, elClass, elId) {
924 throw new Error('select_all_in can not be used any more. Please use jQuery instead.');
928 * @deprecated since Moodle 3.3.
930 function deselect_all_in(elTagName, elClass, elId) {
931 throw new Error('deselect_all_in can not be used any more. Please use jQuery instead.');
935 * @deprecated since Moodle 3.3.
937 function confirm_if(expr, message) {
938 throw new Error('confirm_if can not be used any more.');
942 * @deprecated since Moodle 3.3.
944 function findParentNode(el, elName, elClass, elId) {
945 throw new Error('findParentNode can not be used any more. Please use jQuery instead.');
948 function unmaskPassword(id) {
949 var pw = document.getElementById(id);
950 var chb = document.getElementById(id+'unmask');
952 // MDL-30438 - The capability to changing the value of input type is not supported by IE8 or lower.
953 // Replacing existing child with a new one, removed all yui properties for the node. Therefore, this
954 // functionality won't work in IE8 or lower.
955 // This is a temporary fixed to allow other browsers to function properly.
956 if (Y.UA.ie == 0 || Y.UA.ie >= 9) {
960 pw.type = "password";
962 } else { //IE Browser version 8 or lower
964 // first try IE way - it can not set name attribute later
966 var newpw = document.createElement('<input type="text" autocomplete="off" name="'+pw.name+'">');
968 var newpw = document.createElement('<input type="password" autocomplete="off" name="'+pw.name+'">');
970 newpw.attributes['class'].nodeValue = pw.attributes['class'].nodeValue;
972 var newpw = document.createElement('input');
973 newpw.setAttribute('autocomplete', 'off');
974 newpw.setAttribute('name', pw.name);
976 newpw.setAttribute('type', 'text');
978 newpw.setAttribute('type', 'password');
980 newpw.setAttribute('class', pw.getAttribute('class'));
983 newpw.size = pw.size;
984 newpw.onblur = pw.onblur;
985 newpw.onchange = pw.onchange;
986 newpw.value = pw.value;
987 pw.parentNode.replaceChild(newpw, pw);
992 * @deprecated since Moodle 3.3.
994 function filterByParent(elCollection, parentFinder) {
995 throw new Error('filterByParent can not be used any more. Please use jQuery instead.');
999 * @deprecated since Moodle 3.3, but shouldn't be used in earlier versions either.
1001 function fix_column_widths() {
1002 Y.log('fix_column_widths() no longer does anything. Please remove it from your code.', 'warn', 'javascript-static.js');
1006 * @deprecated since Moodle 3.3, but shouldn't be used in earlier versions either.
1008 function fix_column_width(colName) {
1009 Y.log('fix_column_width() no longer does anything. Please remove it from your code.', 'warn', 'javascript-static.js');
1014 Insert myValue at current cursor position
1016 function insertAtCursor(myField, myValue) {
1018 if (document.selection) {
1020 sel = document.selection.createRange();
1023 // Mozilla/Netscape support
1024 else if (myField.selectionStart || myField.selectionStart == '0') {
1025 var startPos = myField.selectionStart;
1026 var endPos = myField.selectionEnd;
1027 myField.value = myField.value.substring(0, startPos)
1028 + myValue + myField.value.substring(endPos, myField.value.length);
1030 myField.value += myValue;
1035 * Increment a file name.
1037 * @param string file name.
1038 * @param boolean ignoreextension do not extract the extension prior to appending the
1039 * suffix. Useful when incrementing folder names.
1040 * @return string the incremented file name.
1042 function increment_filename(filename, ignoreextension) {
1044 var basename = filename;
1046 // Split the file name into the basename + extension.
1047 if (!ignoreextension) {
1048 var dotpos = filename.lastIndexOf('.');
1049 if (dotpos !== -1) {
1050 basename = filename.substr(0, dotpos);
1051 extension = filename.substr(dotpos, filename.length);
1055 // Look to see if the name already has (NN) at the end of it.
1057 var hasnumber = basename.match(/^(.*) \((\d+)\)$/);
1058 if (hasnumber !== null) {
1059 // Note the current number & remove it from the basename.
1060 number = parseInt(hasnumber[2], 10);
1061 basename = hasnumber[1];
1065 var newname = basename + ' (' + number + ')' + extension;
1070 * Return whether we are in right to left mode or not.
1074 function right_to_left() {
1075 var body = Y.one('body');
1077 if (body && body.hasClass('dir-rtl')) {
1083 function openpopup(event, args) {
1086 if (event.preventDefault) {
1087 event.preventDefault();
1089 event.returnValue = false;
1093 // Make sure the name argument is set and valid.
1094 var nameregex = /[^a-z0-9_]/i;
1095 if (typeof args.name !== 'string') {
1096 args.name = '_blank';
1097 } else if (args.name.match(nameregex)) {
1098 // Cleans window name because IE does not support funky ones.
1099 if (M.cfg.developerdebug) {
1100 alert('DEVELOPER NOTICE: Invalid \'name\' passed to openpopup(): ' + args.name);
1102 args.name = args.name.replace(nameregex, '_');
1105 var fullurl = args.url;
1106 if (!args.url.match(/https?:\/\//)) {
1107 fullurl = M.cfg.wwwroot + args.url;
1109 if (args.fullscreen) {
1110 args.options = args.options.
1111 replace(/top=\d+/, 'top=0').
1112 replace(/left=\d+/, 'left=0').
1113 replace(/width=\d+/, 'width=' + screen.availWidth).
1114 replace(/height=\d+/, 'height=' + screen.availHeight);
1116 var windowobj = window.open(fullurl,args.name,args.options);
1121 if (args.fullscreen) {
1122 // In some browser / OS combinations (E.g. Chrome on Windows), the
1123 // window initially opens slighly too big. The width and heigh options
1124 // seem to control the area inside the browser window, so what with
1125 // scroll-bars, etc. the actual window is bigger than the screen.
1126 // Therefore, we need to fix things up after the window is open.
1127 var hackcount = 100;
1128 var get_size_exactly_right = function() {
1129 windowobj.moveTo(0, 0);
1130 windowobj.resizeTo(screen.availWidth, screen.availHeight);
1132 // Unfortunately, it seems that in Chrome on Ubuntu, if you call
1133 // something like windowobj.resizeTo(1280, 1024) too soon (up to
1134 // about 50ms) after the window is open, then it actually behaves
1135 // as if you called windowobj.resizeTo(0, 0). Therefore, we need to
1136 // check that the resize actually worked, and if not, repeatedly try
1137 // again after a short delay until it works (but with a limit of
1138 // hackcount repeats.
1139 if (hackcount > 0 && (windowobj.innerHeight < 10 || windowobj.innerWidth < 10)) {
1141 setTimeout(get_size_exactly_right, 10);
1144 setTimeout(get_size_exactly_right, 0);
1151 /** Close the current browser window. */
1152 function close_window(e) {
1153 if (e.preventDefault) {
1156 e.returnValue = false;
1162 * Tranfer keyboard focus to the HTML element with the given id, if it exists.
1163 * @param controlid the control id.
1165 function focuscontrol(controlid) {
1166 var control = document.getElementById(controlid);
1173 * Transfers keyboard focus to an HTML element based on the old style style of focus
1174 * This function should be removed as soon as it is no longer used
1176 function old_onload_focus(formid, controlname) {
1177 if (document.forms[formid] && document.forms[formid].elements && document.forms[formid].elements[controlname]) {
1178 document.forms[formid].elements[controlname].focus();
1182 function build_querystring(obj) {
1183 return convert_object_to_string(obj, '&');
1186 function build_windowoptionsstring(obj) {
1187 return convert_object_to_string(obj, ',');
1190 function convert_object_to_string(obj, separator) {
1191 if (typeof obj !== 'object') {
1196 k = encodeURIComponent(k);
1198 if(obj[k] instanceof Array) {
1199 for(var i in value) {
1200 list.push(k+'[]='+encodeURIComponent(value[i]));
1203 list.push(k+'='+encodeURIComponent(value));
1206 return list.join(separator);
1210 * @deprecated since Moodle 3.3.
1212 function stripHTML(str) {
1213 throw new Error('stripHTML can not be used any more. Please use jQuery instead.');
1216 function updateProgressBar(id, percent, msg, estimate) {
1218 el = document.getElementById(id),
1225 eventData.message = msg;
1226 eventData.percent = percent;
1227 eventData.estimate = estimate;
1230 event = new CustomEvent('update', {
1235 } catch (exception) {
1236 if (!(exception instanceof TypeError)) {
1239 event = document.createEvent('CustomEvent');
1240 event.initCustomEvent('update', false, true, eventData);
1241 event.prototype = window.Event.prototype;
1244 el.dispatchEvent(event);
1247 M.util.help_popups = {
1248 setup : function(Y) {
1249 Y.one('body').delegate('click', this.open_popup, 'a.helplinkpopup', this);
1251 open_popup : function(e) {
1252 // Prevent the default page action
1255 // Grab the anchor that was clicked
1256 var anchor = e.target.ancestor('a', true);
1259 'url' : anchor.getAttribute('href'),
1277 args.options = options.join(',');
1284 * Custom menu namespace
1286 M.core_custom_menu = {
1288 * This method is used to initialise a custom menu given the id that belongs
1289 * to the custom menu's root node.
1292 * @param {string} nodeid
1294 init : function(Y, nodeid) {
1295 var node = Y.one('#'+nodeid);
1297 Y.use('node-menunav', function(Y) {
1299 // Remove the javascript-disabled class.... obviously javascript is enabled.
1300 node.removeClass('javascript-disabled');
1301 // Initialise the menunav plugin
1302 node.plug(Y.Plugin.NodeMenuNav);
1309 * Used to store form manipulation methods and enhancments
1311 M.form = M.form || {};
1314 * Converts a nbsp indented select box into a multi drop down custom control much
1315 * like the custom menu. Can no longer be used.
1316 * @deprecated since Moodle 3.3
1318 M.form.init_smartselect = function() {
1319 throw new Error('M.form.init_smartselect can not be used any more.');
1323 * Initiates the listeners for skiplink interaction
1327 M.util.init_skiplink = function(Y) {
1328 Y.one(Y.config.doc.body).delegate('click', function(e) {
1330 e.stopPropagation();
1331 var node = Y.one(this.getAttribute('href'));
1332 node.setAttribute('tabindex', '-1');