1 /* eslint-disable camelcase */
2 // Miscellaneous core Javascript functions for Moodle
3 // Global M object is initilised in inline javascript
6 * Add module to list of available modules that can be loaded from YUI.
7 * @param {Array} modules
9 M.yui.add_module = function(modules) {
10 for (var modname in modules) {
11 YUI_config.modules[modname] = modules[modname];
13 // Ensure thaat the YUI_config is applied to the main YUI instance.
14 Y.applyConfig(YUI_config);
17 * The gallery version to use when loading YUI modules from the gallery.
18 * Will be changed every time when using local galleries.
20 M.yui.galleryversion = '2010.04.21-21-51';
23 * Various utility functions
25 M.util = M.util || {};
28 * Language strings - initialised from page footer.
33 * Returns url for images.
34 * @param {String} imagename
35 * @param {String} component
38 M.util.image_url = function(imagename, component) {
40 if (!component || component == '' || component == 'moodle' || component == 'core') {
44 var url = M.cfg.wwwroot + '/theme/image.php';
45 if (M.cfg.themerev > 0 && M.cfg.slasharguments == 1) {
46 if (!M.cfg.svgicons) {
49 url += '/' + M.cfg.theme + '/' + component + '/' + M.cfg.themerev + '/' + imagename;
51 url += '?theme=' + M.cfg.theme + '&component=' + component + '&rev=' + M.cfg.themerev + '&image=' + imagename;
52 if (!M.cfg.svgicons) {
60 M.util.in_array = function(item, array) {
61 return array.indexOf(item) !== -1;
65 * Init a collapsible region, see print_collapsible_region in weblib.php
66 * @param {YUI} Y YUI3 instance with all libraries loaded
67 * @param {String} id the HTML id for the div.
68 * @param {String} userpref the user preference that records the state of this box. false if none.
69 * @param {String} strtooltip
71 M.util.init_collapsible_region = function(Y, id, userpref, strtooltip) {
72 Y.use('anim', function(Y) {
73 new M.util.CollapsibleRegion(Y, id, userpref, strtooltip);
78 * Object to handle a collapsible region : instantiate and forget styled object
82 * @param {YUI} Y YUI3 instance with all libraries loaded
83 * @param {String} id The HTML id for the div.
84 * @param {String} userpref The user preference that records the state of this box. false if none.
85 * @param {String} strtooltip
87 M.util.CollapsibleRegion = function(Y, id, userpref, strtooltip) {
88 // Record the pref name
89 this.userpref = userpref;
91 // Find the divs in the document.
92 this.div = Y.one('#'+id);
94 // Get the caption for the collapsible region
95 var caption = this.div.one('#'+id + '_caption');
98 var a = Y.Node.create('<a href="#"></a>');
99 a.setAttribute('title', strtooltip);
101 // Get all the nodes from caption, remove them and append them to <a>
102 while (caption.hasChildNodes()) {
103 child = caption.get('firstChild');
109 // Get the height of the div at this point before we shrink it if required
110 var height = this.div.get('offsetHeight');
111 var collapsedimage = 't/collapsed'; // ltr mode
112 if (right_to_left()) {
113 collapsedimage = 't/collapsed_rtl';
115 collapsedimage = 't/collapsed';
117 if (this.div.hasClass('collapsed')) {
118 // Add the correct image and record the YUI node created in the process
119 this.icon = Y.Node.create('<img src="'+M.util.image_url(collapsedimage, 'moodle')+'" alt="" />');
120 // Shrink the div as it is collapsed by default
121 this.div.setStyle('height', caption.get('offsetHeight')+'px');
123 // Add the correct image and record the YUI node created in the process
124 this.icon = Y.Node.create('<img src="'+M.util.image_url('t/expanded', 'moodle')+'" alt="" />');
128 // Create the animation.
129 var animation = new Y.Anim({
132 easing: Y.Easing.easeBoth,
133 to: {height:caption.get('offsetHeight')},
134 from: {height:height}
137 animation.on('start', () => M.util.js_pending('CollapsibleRegion'));
138 animation.on('resume', () => M.util.js_pending('CollapsibleRegion'));
139 animation.on('pause', () => M.util.js_complete('CollapsibleRegion'));
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'));
156 M.util.js_complete('CollapsibleRegion');
159 // Hook up the event handler.
160 a.on('click', function(e, animation) {
162 // Animate to the appropriate size.
163 if (animation.get('running')) {
166 animation.set('reverse', this.div.hasClass('collapsed'));
167 // Update the user preference.
169 require(['core_user/repository'], function(UserRepository) {
170 UserRepository.setUserPreference(this.userpref, !this.div.hasClass('collapsed'));
178 * The user preference that stores the state of this box.
182 M.util.CollapsibleRegion.prototype.userpref = null;
185 * The key divs that make up this
189 M.util.CollapsibleRegion.prototype.div = null;
192 * The key divs that make up this
196 M.util.CollapsibleRegion.prototype.icon = null;
199 * Makes a best effort to connect back to Moodle to update a user preference,
200 * however, there is no mechanism for finding out if the update succeeded.
202 * Before you can use this function in your JavsScript, you must have called
203 * user_preference_allow_ajax_update from moodlelib.php to tell Moodle that
204 * the udpate is allowed, and how to safely clean and submitted values.
206 * @param {String} name the name of the setting to update.
207 * @param {String} value the value to set it to.
209 * @deprecated since Moodle 4.3.
211 M.util.set_user_preference = function(name, value) {
212 Y.log('M.util.set_user_preference is deprecated. Please use the "core_user/repository" module instead.', 'warn');
214 require(['core_user/repository'], function(UserRepository) {
215 UserRepository.setUserPreference(name, value);
220 * Prints a confirmation dialog in the style of DOM.confirm().
222 * @method show_confirm_dialog
223 * @param {EventFacade} e
224 * @param {Object} args
225 * @param {String} args.message The question to ask the user
226 * @param {Function} [args.callback] A callback to apply on confirmation.
227 * @param {Object} [args.scope] The scope to use when calling the callback.
228 * @param {Object} [args.callbackargs] Any arguments to pass to the callback.
229 * @param {String} [args.cancellabel] The label to use on the cancel button.
230 * @param {String} [args.continuelabel] The label to use on the continue button.
232 M.util.show_confirm_dialog = (e, {
239 if (e.preventDefault) {
244 ['core/notification', 'core/str', 'core_form/changechecker', 'core/normalise'],
245 function(Notification, Str, FormChangeChecker, Normalise) {
247 if (scope === null && e.target) {
248 // Fall back to the event target if no scope provided.
252 Notification.saveCancelPromise(
253 Str.get_string('confirmation', 'admin'),
255 continuelabel || Str.get_string('yes', 'moodle'),
259 callback.apply(scope, callbackargs);
264 window.console.error(
265 `M.util.show_confirm_dialog: No target found for event`,
271 const target = Normalise.getElement(e.target);
273 if (target.closest('a')) {
274 window.location = target.closest('a').getAttribute('href');
276 } else if (target.closest('input') || target.closest('button')) {
277 const form = target.closest('form');
278 const hiddenValue = document.createElement('input');
279 hiddenValue.setAttribute('type', 'hidden');
280 hiddenValue.setAttribute('name', target.getAttribute('name'));
281 hiddenValue.setAttribute('value', target.getAttribute('value'));
282 form.appendChild(hiddenValue);
283 FormChangeChecker.markFormAsDirty(form);
286 } else if (target.closest('form')) {
287 const form = target.closest('form');
288 FormChangeChecker.markFormAsDirty(form);
292 window.console.error(
293 `Element of type ${target.tagName} is not supported by M.util.show_confirm_dialog.`
306 /** Useful for full embedding of various stuff */
307 M.util.init_maximised_embed = function(Y, id) {
308 var obj = Y.one('#'+id);
313 var get_htmlelement_size = function(el, prop) {
314 if (Y.Lang.isString(el)) {
315 el = Y.one('#' + el);
317 // Ensure element exists.
319 var val = el.getStyle(prop);
321 val = el.getComputedStyle(prop);
333 var resize_object = function() {
334 obj.setStyle('display', 'none');
335 var newwidth = get_htmlelement_size('maincontent', 'width') - 35;
337 if (newwidth > 500) {
338 obj.setStyle('width', newwidth + 'px');
340 obj.setStyle('width', '500px');
343 var headerheight = get_htmlelement_size('page-header', 'height');
344 var footerheight = get_htmlelement_size('page-footer', 'height');
345 var newheight = parseInt(Y.one('body').get('docHeight')) - footerheight - headerheight - 100;
346 if (newheight < 400) {
349 obj.setStyle('height', newheight+'px');
350 obj.setStyle('display', '');
354 // fix layout if window resized too
355 Y.use('event-resize', function (Y) {
356 Y.on("windowresize", function() {
363 * Breaks out all links to the top frame - used in frametop page layout.
365 M.util.init_frametop = function(Y) {
366 Y.all('a').each(function(node) {
367 node.set('target', '_top');
369 Y.all('form').each(function(node) {
370 node.set('target', '_top');
375 * @deprecated since Moodle 3.3
377 M.util.init_toggle_class_on_click = function(Y, id, cssselector, toggleclassname, togglecssselector) {
378 throw new Error('M.util.init_toggle_class_on_click can not be used any more. Please use jQuery instead.');
382 * Initialises a colour picker
384 * Designed to be used with admin_setting_configcolourpicker although could be used
385 * anywhere, just give a text input an id and insert a div with the class admin_colourpicker
386 * above or below the input (must have the same parent) and then call this with the
389 * This code was mostly taken from my [Sam Hemelryk] css theme tool available in
390 * contrib/blocks. For better docs refer to that.
394 * @param {object} previewconf
396 M.util.init_colour_picker = function(Y, id, previewconf) {
398 * We need node and event-mouseenter
400 Y.use('node', 'event-mouseenter', function(){
402 * The colour picker object
411 eventMouseEnter : null,
412 eventMouseLeave : null,
413 eventMouseMove : null,
418 * Initalises the colour picker by putting everything together and wiring the events
421 this.input = Y.one('#'+id);
422 this.box = this.input.ancestor().one('.admin_colourpicker');
423 this.image = Y.Node.create('<img alt="" class="colourdialogue" />');
424 this.image.setAttribute('src', M.util.image_url('i/colourpicker', 'moodle'));
425 this.preview = Y.Node.create('<div class="previewcolour"></div>');
426 this.preview.setStyle('width', this.height/2).setStyle('height', this.height/2).setStyle('backgroundColor', this.input.get('value'));
427 this.current = Y.Node.create('<div class="currentcolour"></div>');
428 this.current.setStyle('width', this.height/2).setStyle('height', this.height/2 -1).setStyle('backgroundColor', this.input.get('value'));
429 this.box.setContent('').append(this.image).append(this.preview).append(this.current);
431 if (typeof(previewconf) === 'object' && previewconf !== null) {
432 Y.one('#'+id+'_preview').on('click', function(e){
433 if (Y.Lang.isString(previewconf.selector)) {
434 Y.all(previewconf.selector).setStyle(previewconf.style, this.input.get('value'));
436 for (var i in previewconf.selector) {
437 Y.all(previewconf.selector[i]).setStyle(previewconf.style, this.input.get('value'));
443 this.eventClick = this.image.on('click', this.pickColour, this);
444 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
447 * Starts to follow the mouse once it enter the image
449 startFollow : function(e) {
450 this.eventMouseEnter.detach();
451 this.eventMouseLeave = Y.on('mouseleave', this.endFollow, this.image, this);
452 this.eventMouseMove = this.image.on('mousemove', function(e){
453 this.preview.setStyle('backgroundColor', this.determineColour(e));
457 * Stops following the mouse
459 endFollow : function(e) {
460 this.eventMouseMove.detach();
461 this.eventMouseLeave.detach();
462 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
465 * Picks the colour the was clicked on
467 pickColour : function(e) {
468 var colour = this.determineColour(e);
469 this.input.set('value', colour);
470 this.current.setStyle('backgroundColor', colour);
473 * Calculates the colour fromthe given co-ordinates
475 determineColour : function(e) {
476 var eventx = Math.floor(e.pageX-e.target.getX());
477 var eventy = Math.floor(e.pageY-e.target.getY());
479 var imagewidth = this.width;
480 var imageheight = this.height;
481 var factor = this.factor;
482 var colour = [255,0,0];
493 var matrixcount = matrices.length;
494 var limit = Math.round(imagewidth/matrixcount);
495 var heightbreak = Math.round(imageheight/2);
497 for (var x = 0; x < imagewidth; x++) {
498 var divisor = Math.floor(x / limit);
499 var matrix = matrices[divisor];
501 colour[0] += matrix[0]*factor;
502 colour[1] += matrix[1]*factor;
503 colour[2] += matrix[2]*factor;
510 var pixel = [colour[0], colour[1], colour[2]];
511 if (eventy < heightbreak) {
512 pixel[0] += Math.floor(((255-pixel[0])/heightbreak) * (heightbreak - eventy));
513 pixel[1] += Math.floor(((255-pixel[1])/heightbreak) * (heightbreak - eventy));
514 pixel[2] += Math.floor(((255-pixel[2])/heightbreak) * (heightbreak - eventy));
515 } else if (eventy > heightbreak) {
516 pixel[0] = Math.floor((imageheight-eventy)*(pixel[0]/heightbreak));
517 pixel[1] = Math.floor((imageheight-eventy)*(pixel[1]/heightbreak));
518 pixel[2] = Math.floor((imageheight-eventy)*(pixel[2]/heightbreak));
521 return this.convert_rgb_to_hex(pixel);
524 * Converts an RGB value to Hex
526 convert_rgb_to_hex : function(rgb) {
528 var hexchars = "0123456789ABCDEF";
529 for (var i=0; i<3; i++) {
530 var number = Math.abs(rgb[i]);
531 if (number == 0 || isNaN(number)) {
534 hex += hexchars.charAt((number-number%16)/16)+hexchars.charAt(number%16);
541 * Initialise the colour picker :) Hoorah
547 M.util.init_block_hider = function(Y, config) {
548 Y.use('base', 'node', function(Y) {
549 M.util.block_hider = M.util.block_hider || (function(){
550 var blockhider = function() {
551 blockhider.superclass.constructor.apply(this, arguments);
553 blockhider.prototype = {
554 initializer : function(config) {
555 this.set('block', '#'+this.get('id'));
556 var b = this.get('block'),
561 if (t && (a = t.one('.block_action'))) {
562 hide = Y.Node.create('<img />')
563 .addClass('block-hider-hide')
565 alt: config.tooltipVisible,
566 src: this.get('iconVisible'),
568 'title': config.tooltipVisible
570 hide.on('keypress', this.updateStateKey, this, true);
571 hide.on('click', this.updateState, this, true);
573 show = Y.Node.create('<img />')
574 .addClass('block-hider-show')
576 alt: config.tooltipHidden,
577 src: this.get('iconHidden'),
579 'title': config.tooltipHidden
581 show.on('keypress', this.updateStateKey, this, false);
582 show.on('click', this.updateState, this, false);
584 a.insert(show, 0).insert(hide, 0);
587 updateState : function(e, hide) {
588 require(['core_user/repository'], function(UserRepository) {
589 UserRepository.setUserPreference(this.get('preference'), hide);
592 this.get('block').addClass('hidden');
593 this.get('block').one('.block-hider-show').focus();
595 this.get('block').removeClass('hidden');
596 this.get('block').one('.block-hider-hide').focus();
599 updateStateKey : function(e, hide) {
600 if (e.keyCode == 13) { //allow hide/show via enter key
601 this.updateState(this, hide);
605 Y.extend(blockhider, Y.Base, blockhider.prototype, {
611 value : M.util.image_url('t/switch_minus', 'moodle')
614 value : M.util.image_url('t/switch_plus', 'moodle')
617 setter : function(node) {
625 new M.util.block_hider(config);
630 * @var pending_js - The keys are the list of all pending js actions.
633 M.util.pending_js = [];
634 M.util.complete_js = [];
637 * Register any long running javascript code with a unique identifier.
638 * This is used to ensure that Behat steps do not continue with interactions until the page finishes loading.
640 * All calls to M.util.js_pending _must_ be followed by a subsequent call to M.util.js_complete with the same exact
643 * This function may also be called with no arguments to test if there is any js calls pending.
645 * The uniqid specified may be any Object, including Number, String, or actual Object; however please note that the
646 * paired js_complete function performs a strict search for the key specified. As such, if using an Object, the exact
647 * Object must be passed into both functions.
649 * @param {Mixed} uniqid Register long-running code against the supplied identifier
650 * @return {Number} Number of pending items
652 M.util.js_pending = function(uniqid) {
653 if (typeof uniqid !== 'undefined') {
654 M.util.pending_js.push(uniqid);
657 return M.util.pending_js.length;
661 M.util.js_pending('init');
664 * Register listeners for Y.io start/end so we can wait for them in behat.
666 YUI.add('moodle-core-io', function(Y) {
667 Y.on('io:start', function(id) {
668 M.util.js_pending('io:' + id);
670 Y.on('io:end', function(id) {
671 M.util.js_complete('io:' + id);
681 * Unregister some long running javascript code using the unique identifier specified in M.util.js_pending.
683 * This function must be matched with an identical call to M.util.js_pending.
685 * @param {Mixed} uniqid Register long-running code against the supplied identifier
686 * @return {Number} Number of pending items remaining after removing this item
688 M.util.js_complete = function(uniqid) {
689 const index = M.util.pending_js.indexOf(uniqid);
691 M.util.complete_js.push(M.util.pending_js.splice(index, 1)[0]);
693 window.console.log("Unable to locate key for js_complete call", uniqid);
696 return M.util.pending_js.length;
700 * Returns a string registered in advance for usage in JavaScript
702 * If you do not pass the third parameter, the function will just return
703 * the corresponding value from the M.str object. If the third parameter is
704 * provided, the function performs {$a} placeholder substitution in the
705 * same way as PHP get_string() in Moodle does.
707 * @param {String} identifier string identifier
708 * @param {String} component the component providing the string
709 * @param {Object|String} [a] optional variable to populate placeholder with
711 M.util.get_string = function(identifier, component, a) {
714 if (M.cfg.developerdebug) {
715 // creating new instance if YUI is not optimal but it seems to be better way then
716 // require the instance via the function API - note that it is used in rare cases
717 // for debugging only anyway
718 // To ensure we don't kill browser performance if hundreds of get_string requests
719 // are made we cache the instance we generate within the M.util namespace.
720 // We don't publicly define the variable so that it doesn't get abused.
721 if (typeof M.util.get_string_yui_instance === 'undefined') {
722 M.util.get_string_yui_instance = new YUI({ debug : true });
724 var Y = M.util.get_string_yui_instance;
727 if (!M.str.hasOwnProperty(component) || !M.str[component].hasOwnProperty(identifier)) {
728 stringvalue = '[[' + identifier + ',' + component + ']]';
729 if (M.cfg.developerdebug) {
730 Y.log('undefined string ' + stringvalue, 'warn', 'M.util.get_string');
735 stringvalue = M.str[component][identifier];
737 if (typeof a == 'undefined') {
738 // no placeholder substitution requested
742 if (typeof a == 'number' || typeof a == 'string') {
743 // replace all occurrences of {$a} with the placeholder value
744 stringvalue = stringvalue.replace(/\{\$a\}/g, a);
748 if (typeof a == 'object') {
749 // replace {$a->key} placeholders
751 if (typeof a[key] != 'number' && typeof a[key] != 'string') {
752 if (M.cfg.developerdebug) {
753 Y.log('invalid value type for $a->' + key, 'warn', 'M.util.get_string');
757 var search = '{$a->' + key + '}';
758 search = search.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
759 search = new RegExp(search, 'g');
760 stringvalue = stringvalue.replace(search, a[key]);
765 if (M.cfg.developerdebug) {
766 Y.log('incorrect placeholder type', 'warn', 'M.util.get_string');
772 * Set focus on username or password field of the login form.
773 * @deprecated since Moodle 3.3.
775 M.util.focus_login_form = function(Y) {
776 Y.log('M.util.focus_login_form no longer does anything. Please use jquery instead.', 'warn', 'javascript-static.js');
780 * Set focus on login error message.
781 * @deprecated since Moodle 3.3.
783 M.util.focus_login_error = function(Y) {
784 Y.log('M.util.focus_login_error no longer does anything. Please use jquery instead.', 'warn', 'javascript-static.js');
788 * Adds lightbox hidden element that covers the whole node.
791 * @param {Node} the node lightbox should be added to
792 * @retun {Node} created lightbox node
794 M.util.add_lightbox = function(Y, node) {
795 var WAITICON = {'pix':"i/loading_small",'component':'moodle'};
797 // Check if lightbox is already there
798 if (node.one('.lightbox')) {
799 return node.one('.lightbox');
802 node.setStyle('position', 'relative');
803 var waiticon = Y.Node.create('<img />')
805 'src' : M.util.image_url(WAITICON.pix, WAITICON.component)
808 'position' : 'relative',
812 var lightbox = Y.Node.create('<div></div>')
815 'position' : 'absolute',
820 'backgroundColor' : 'white',
821 'textAlign' : 'center'
823 .setAttribute('class', 'lightbox')
826 lightbox.appendChild(waiticon);
827 node.append(lightbox);
832 * Appends a hidden spinner element to the specified node.
835 * @param {Node} the node the spinner should be added to
836 * @return {Node} created spinner node
838 M.util.add_spinner = function(Y, node) {
839 var WAITICON = {'pix':"i/loading_small",'component':'moodle'};
841 // Check if spinner is already there
842 if (node.one('.spinner')) {
843 return node.one('.spinner');
846 var spinner = Y.Node.create('<img />')
847 .setAttribute('src', M.util.image_url(WAITICON.pix, WAITICON.component))
849 .addClass('iconsmall')
852 node.append(spinner);
857 * @deprecated since Moodle 3.3.
859 function checkall() {
860 throw new Error('checkall can not be used any more. Please use jQuery instead.');
864 * @deprecated since Moodle 3.3.
866 function checknone() {
867 throw new Error('checknone can not be used any more. Please use jQuery instead.');
871 * @deprecated since Moodle 3.3.
873 function select_all_in_element_with_id(id, checked) {
874 throw new Error('select_all_in_element_with_id can not be used any more. Please use jQuery instead.');
878 * @deprecated since Moodle 3.3.
880 function select_all_in(elTagName, elClass, elId) {
881 throw new Error('select_all_in can not be used any more. Please use jQuery instead.');
885 * @deprecated since Moodle 3.3.
887 function deselect_all_in(elTagName, elClass, elId) {
888 throw new Error('deselect_all_in can not be used any more. Please use jQuery instead.');
892 * @deprecated since Moodle 3.3.
894 function confirm_if(expr, message) {
895 throw new Error('confirm_if can not be used any more.');
899 * @deprecated since Moodle 3.3.
901 function findParentNode(el, elName, elClass, elId) {
902 throw new Error('findParentNode can not be used any more. Please use jQuery instead.');
905 function unmaskPassword(id) {
906 var pw = document.getElementById(id);
907 var chb = document.getElementById(id+'unmask');
909 // MDL-30438 - The capability to changing the value of input type is not supported by IE8 or lower.
910 // Replacing existing child with a new one, removed all yui properties for the node. Therefore, this
911 // functionality won't work in IE8 or lower.
912 // This is a temporary fixed to allow other browsers to function properly.
913 if (Y.UA.ie == 0 || Y.UA.ie >= 9) {
917 pw.type = "password";
919 } else { //IE Browser version 8 or lower
921 // first try IE way - it can not set name attribute later
923 var newpw = document.createElement('<input type="text" autocomplete="off" name="'+pw.name+'">');
925 var newpw = document.createElement('<input type="password" autocomplete="off" name="'+pw.name+'">');
927 newpw.attributes['class'].nodeValue = pw.attributes['class'].nodeValue;
929 var newpw = document.createElement('input');
930 newpw.setAttribute('autocomplete', 'off');
931 newpw.setAttribute('name', pw.name);
933 newpw.setAttribute('type', 'text');
935 newpw.setAttribute('type', 'password');
937 newpw.setAttribute('class', pw.getAttribute('class'));
940 newpw.size = pw.size;
941 newpw.onblur = pw.onblur;
942 newpw.onchange = pw.onchange;
943 newpw.value = pw.value;
944 pw.parentNode.replaceChild(newpw, pw);
949 * @deprecated since Moodle 3.3.
951 function filterByParent(elCollection, parentFinder) {
952 throw new Error('filterByParent can not be used any more. Please use jQuery instead.');
956 * @deprecated since Moodle 3.3, but shouldn't be used in earlier versions either.
958 function fix_column_widths() {
959 Y.log('fix_column_widths() no longer does anything. Please remove it from your code.', 'warn', 'javascript-static.js');
963 * @deprecated since Moodle 3.3, but shouldn't be used in earlier versions either.
965 function fix_column_width(colName) {
966 Y.log('fix_column_width() no longer does anything. Please remove it from your code.', 'warn', 'javascript-static.js');
971 Insert myValue at current cursor position
973 function insertAtCursor(myField, myValue) {
975 if (document.selection) {
977 sel = document.selection.createRange();
980 // Mozilla/Netscape support
981 else if (myField.selectionStart || myField.selectionStart == '0') {
982 var startPos = myField.selectionStart;
983 var endPos = myField.selectionEnd;
984 myField.value = myField.value.substring(0, startPos)
985 + myValue + myField.value.substring(endPos, myField.value.length);
987 myField.value += myValue;
992 * Increment a file name.
994 * @param string file name.
995 * @param boolean ignoreextension do not extract the extension prior to appending the
996 * suffix. Useful when incrementing folder names.
997 * @return string the incremented file name.
999 function increment_filename(filename, ignoreextension) {
1001 var basename = filename;
1003 // Split the file name into the basename + extension.
1004 if (!ignoreextension) {
1005 var dotpos = filename.lastIndexOf('.');
1006 if (dotpos !== -1) {
1007 basename = filename.substr(0, dotpos);
1008 extension = filename.substr(dotpos, filename.length);
1012 // Look to see if the name already has (NN) at the end of it.
1014 var hasnumber = basename.match(/^(.*) \((\d+)\)$/);
1015 if (hasnumber !== null) {
1016 // Note the current number & remove it from the basename.
1017 number = parseInt(hasnumber[2], 10);
1018 basename = hasnumber[1];
1022 var newname = basename + ' (' + number + ')' + extension;
1027 * Return whether we are in right to left mode or not.
1031 function right_to_left() {
1032 var body = Y.one('body');
1034 if (body && body.hasClass('dir-rtl')) {
1040 function openpopup(event, args) {
1043 if (event.preventDefault) {
1044 event.preventDefault();
1046 event.returnValue = false;
1050 // Make sure the name argument is set and valid.
1051 var nameregex = /[^a-z0-9_]/i;
1052 if (typeof args.name !== 'string') {
1053 args.name = '_blank';
1054 } else if (args.name.match(nameregex)) {
1055 // Cleans window name because IE does not support funky ones.
1056 if (M.cfg.developerdebug) {
1057 alert('DEVELOPER NOTICE: Invalid \'name\' passed to openpopup(): ' + args.name);
1059 args.name = args.name.replace(nameregex, '_');
1062 var fullurl = args.url;
1063 if (!args.url.match(/https?:\/\//)) {
1064 fullurl = M.cfg.wwwroot + args.url;
1066 if (args.fullscreen) {
1067 args.options = args.options.
1068 replace(/top=\d+/, 'top=0').
1069 replace(/left=\d+/, 'left=0').
1070 replace(/width=\d+/, 'width=' + screen.availWidth).
1071 replace(/height=\d+/, 'height=' + screen.availHeight);
1073 var windowobj = window.open(fullurl,args.name,args.options);
1078 if (args.fullscreen) {
1079 // In some browser / OS combinations (E.g. Chrome on Windows), the
1080 // window initially opens slighly too big. The width and heigh options
1081 // seem to control the area inside the browser window, so what with
1082 // scroll-bars, etc. the actual window is bigger than the screen.
1083 // Therefore, we need to fix things up after the window is open.
1084 var hackcount = 100;
1085 var get_size_exactly_right = function() {
1086 windowobj.moveTo(0, 0);
1087 windowobj.resizeTo(screen.availWidth, screen.availHeight);
1089 // Unfortunately, it seems that in Chrome on Ubuntu, if you call
1090 // something like windowobj.resizeTo(1280, 1024) too soon (up to
1091 // about 50ms) after the window is open, then it actually behaves
1092 // as if you called windowobj.resizeTo(0, 0). Therefore, we need to
1093 // check that the resize actually worked, and if not, repeatedly try
1094 // again after a short delay until it works (but with a limit of
1095 // hackcount repeats.
1096 if (hackcount > 0 && (windowobj.innerHeight < 10 || windowobj.innerWidth < 10)) {
1098 setTimeout(get_size_exactly_right, 10);
1101 setTimeout(get_size_exactly_right, 0);
1108 /** Close the current browser window. */
1109 function close_window(e) {
1110 if (e.preventDefault) {
1113 e.returnValue = false;
1119 * Tranfer keyboard focus to the HTML element with the given id, if it exists.
1120 * @param controlid the control id.
1122 function focuscontrol(controlid) {
1123 var control = document.getElementById(controlid);
1130 * Transfers keyboard focus to an HTML element based on the old style style of focus
1131 * This function should be removed as soon as it is no longer used
1133 function old_onload_focus(formid, controlname) {
1134 if (document.forms[formid] && document.forms[formid].elements && document.forms[formid].elements[controlname]) {
1135 document.forms[formid].elements[controlname].focus();
1139 function build_querystring(obj) {
1140 return convert_object_to_string(obj, '&');
1143 function build_windowoptionsstring(obj) {
1144 return convert_object_to_string(obj, ',');
1147 function convert_object_to_string(obj, separator) {
1148 if (typeof obj !== 'object') {
1153 k = encodeURIComponent(k);
1155 if(obj[k] instanceof Array) {
1156 for(var i in value) {
1157 list.push(k+'[]='+encodeURIComponent(value[i]));
1160 list.push(k+'='+encodeURIComponent(value));
1163 return list.join(separator);
1167 * @deprecated since Moodle 3.3.
1169 function stripHTML(str) {
1170 throw new Error('stripHTML can not be used any more. Please use jQuery instead.');
1173 // eslint-disable-next-line no-unused-vars
1174 function updateProgressBar(id, percent, msg, estimate, error) {
1176 el = document.getElementById(id),
1183 eventData.message = msg;
1184 eventData.percent = percent;
1185 eventData.estimate = estimate;
1186 eventData.error = error;
1189 event = new CustomEvent('update', {
1194 } catch (exception) {
1195 if (!(exception instanceof TypeError)) {
1198 event = document.createEvent('CustomEvent');
1199 event.initCustomEvent('update', false, true, eventData);
1200 event.prototype = window.Event.prototype;
1203 el.dispatchEvent(event);
1206 M.util.help_popups = {
1207 setup : function(Y) {
1208 Y.one('body').delegate('click', this.open_popup, 'a.helplinkpopup', this);
1210 open_popup : function(e) {
1211 // Prevent the default page action
1214 // Grab the anchor that was clicked
1215 var anchor = e.target.ancestor('a', true);
1218 'url' : anchor.getAttribute('href'),
1236 args.options = options.join(',');
1243 * Custom menu namespace
1245 M.core_custom_menu = {
1247 * This method is used to initialise a custom menu given the id that belongs
1248 * to the custom menu's root node.
1251 * @param {string} nodeid
1253 init : function(Y, nodeid) {
1254 var node = Y.one('#'+nodeid);
1256 Y.use('node-menunav', function(Y) {
1258 // Remove the javascript-disabled class.... obviously javascript is enabled.
1259 node.removeClass('javascript-disabled');
1260 // Initialise the menunav plugin
1261 node.plug(Y.Plugin.NodeMenuNav);
1268 * Used to store form manipulation methods and enhancments
1270 M.form = M.form || {};
1273 * Converts a nbsp indented select box into a multi drop down custom control much
1274 * like the custom menu. Can no longer be used.
1275 * @deprecated since Moodle 3.3
1277 M.form.init_smartselect = function() {
1278 throw new Error('M.form.init_smartselect can not be used any more.');
1282 * Initiates the listeners for skiplink interaction
1286 M.util.init_skiplink = function(Y) {
1287 Y.one(Y.config.doc.body).delegate('click', function(e) {
1289 e.stopPropagation();
1290 var node = Y.one(this.getAttribute('href'));
1291 node.setAttribute('tabindex', '-1');