| 1 | efrain | 1 | /* eslint-disable camelcase */
 | 
        
           |  |  | 2 | // Miscellaneous core Javascript functions for Moodle
 | 
        
           |  |  | 3 | // Global M object is initilised in inline javascript
 | 
        
           |  |  | 4 |   | 
        
           |  |  | 5 | /**
 | 
        
           |  |  | 6 |  * Add module to list of available modules that can be loaded from YUI.
 | 
        
           |  |  | 7 |  * @param {Array} modules
 | 
        
           |  |  | 8 |  */
 | 
        
           |  |  | 9 | M.yui.add_module = function(modules) {
 | 
        
           |  |  | 10 |     for (var modname in modules) {
 | 
        
           |  |  | 11 |         YUI_config.modules[modname] = modules[modname];
 | 
        
           |  |  | 12 |     }
 | 
        
           |  |  | 13 |     // Ensure thaat the YUI_config is applied to the main YUI instance.
 | 
        
           |  |  | 14 |     Y.applyConfig(YUI_config);
 | 
        
           |  |  | 15 | };
 | 
        
           |  |  | 16 | /**
 | 
        
           |  |  | 17 |  * The gallery version to use when loading YUI modules from the gallery.
 | 
        
           |  |  | 18 |  * Will be changed every time when using local galleries.
 | 
        
           |  |  | 19 |  */
 | 
        
           |  |  | 20 | M.yui.galleryversion = '2010.04.21-21-51';
 | 
        
           |  |  | 21 |   | 
        
           |  |  | 22 | /**
 | 
        
           |  |  | 23 |  * Various utility functions
 | 
        
           |  |  | 24 |  */
 | 
        
           |  |  | 25 | M.util = M.util || {};
 | 
        
           |  |  | 26 |   | 
        
           |  |  | 27 | /**
 | 
        
           |  |  | 28 |  * Language strings - initialised from page footer.
 | 
        
           |  |  | 29 |  */
 | 
        
           |  |  | 30 | M.str = M.str || {};
 | 
        
           |  |  | 31 |   | 
        
           |  |  | 32 | /**
 | 
        
           |  |  | 33 |  * Returns url for images.
 | 
        
           |  |  | 34 |  * @param {String} imagename
 | 
        
           |  |  | 35 |  * @param {String} component
 | 
        
           |  |  | 36 |  * @return {String}
 | 
        
           |  |  | 37 |  */
 | 
        
           |  |  | 38 | M.util.image_url = function(imagename, component) {
 | 
        
           |  |  | 39 |   | 
        
           |  |  | 40 |     if (!component || component == '' || component == 'moodle' || component == 'core') {
 | 
        
           |  |  | 41 |         component = 'core';
 | 
        
           |  |  | 42 |     }
 | 
        
           |  |  | 43 |   | 
        
           |  |  | 44 |     var url = M.cfg.wwwroot + '/theme/image.php';
 | 
        
           |  |  | 45 |     if (M.cfg.themerev > 0 && M.cfg.slasharguments == 1) {
 | 
        
           |  |  | 46 |         if (!M.cfg.svgicons) {
 | 
        
           |  |  | 47 |             url += '/_s';
 | 
        
           |  |  | 48 |         }
 | 
        
           |  |  | 49 |         url += '/' + M.cfg.theme + '/' + component + '/' + M.cfg.themerev + '/' + imagename;
 | 
        
           |  |  | 50 |     } else {
 | 
        
           |  |  | 51 |         url += '?theme=' + M.cfg.theme + '&component=' + component + '&rev=' + M.cfg.themerev + '&image=' + imagename;
 | 
        
           |  |  | 52 |         if (!M.cfg.svgicons) {
 | 
        
           |  |  | 53 |             url += '&svg=0';
 | 
        
           |  |  | 54 |         }
 | 
        
           |  |  | 55 |     }
 | 
        
           |  |  | 56 |   | 
        
           |  |  | 57 |     return url;
 | 
        
           |  |  | 58 | };
 | 
        
           |  |  | 59 |   | 
        
           |  |  | 60 | M.util.in_array = function(item, array) {
 | 
        
           |  |  | 61 |     return array.indexOf(item) !== -1;
 | 
        
           |  |  | 62 | };
 | 
        
           |  |  | 63 |   | 
        
           |  |  | 64 | /**
 | 
        
           |  |  | 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
 | 
        
           |  |  | 70 |  */
 | 
        
           |  |  | 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);
 | 
        
           |  |  | 74 |     });
 | 
        
           |  |  | 75 | };
 | 
        
           |  |  | 76 |   | 
        
           |  |  | 77 | /**
 | 
        
           |  |  | 78 |  * Object to handle a collapsible region : instantiate and forget styled object
 | 
        
           |  |  | 79 |  *
 | 
        
           |  |  | 80 |  * @class
 | 
        
           |  |  | 81 |  * @constructor
 | 
        
           |  |  | 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
 | 
        
           |  |  | 86 |  */
 | 
        
           |  |  | 87 | M.util.CollapsibleRegion = function(Y, id, userpref, strtooltip) {
 | 
        
           |  |  | 88 |     // Record the pref name
 | 
        
           |  |  | 89 |     this.userpref = userpref;
 | 
        
           |  |  | 90 |   | 
        
           |  |  | 91 |     // Find the divs in the document.
 | 
        
           |  |  | 92 |     this.div = Y.one('#'+id);
 | 
        
           |  |  | 93 |   | 
        
           |  |  | 94 |     // Get the caption for the collapsible region
 | 
        
           |  |  | 95 |     var caption = this.div.one('#'+id + '_caption');
 | 
        
           |  |  | 96 |   | 
        
           |  |  | 97 |     // Create a link
 | 
        
           |  |  | 98 |     var a = Y.Node.create('<a href="#"></a>');
 | 
        
           |  |  | 99 |     a.setAttribute('title', strtooltip);
 | 
        
           |  |  | 100 |   | 
        
           |  |  | 101 |     // Get all the nodes from caption, remove them and append them to <a>
 | 
        
           |  |  | 102 |     while (caption.hasChildNodes()) {
 | 
        
           |  |  | 103 |         child = caption.get('firstChild');
 | 
        
           |  |  | 104 |         child.remove();
 | 
        
           |  |  | 105 |         a.append(child);
 | 
        
           |  |  | 106 |     }
 | 
        
           |  |  | 107 |     caption.append(a);
 | 
        
           |  |  | 108 |   | 
        
           |  |  | 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';
 | 
        
           |  |  | 114 |     }
 | 
        
           |  |  | 115 |     if (this.div.hasClass('collapsed')) {
 | 
        
           |  |  | 116 |         // Add the correct image and record the YUI node created in the process
 | 
        
           | 1441 | ariadna | 117 |         this.icon = Y.Node.create('<img src="'+M.util.image_url(collapsedimage, 'moodle')+'" alt="" class="icon" />');
 | 
        
           | 1 | efrain | 118 |         // Shrink the div as it is collapsed by default
 | 
        
           |  |  | 119 |         this.div.setStyle('height', caption.get('offsetHeight')+'px');
 | 
        
           |  |  | 120 |     } else {
 | 
        
           |  |  | 121 |         // Add the correct image and record the YUI node created in the process
 | 
        
           | 1441 | ariadna | 122 |         this.icon = Y.Node.create('<img src="'+M.util.image_url('t/expanded', 'moodle')+'" alt="" class="icon" />');
 | 
        
           | 1 | efrain | 123 |     }
 | 
        
           |  |  | 124 |     a.append(this.icon);
 | 
        
           |  |  | 125 |   | 
        
           |  |  | 126 |     // Create the animation.
 | 
        
           |  |  | 127 |     var animation = new Y.Anim({
 | 
        
           |  |  | 128 |         node: this.div,
 | 
        
           |  |  | 129 |         duration: 0.3,
 | 
        
           |  |  | 130 |         easing: Y.Easing.easeBoth,
 | 
        
           |  |  | 131 |         to: {height:caption.get('offsetHeight')},
 | 
        
           |  |  | 132 |         from: {height:height}
 | 
        
           |  |  | 133 |     });
 | 
        
           |  |  | 134 |   | 
        
           | 11 | efrain | 135 |     animation.on('start', () => M.util.js_pending('CollapsibleRegion'));
 | 
        
           |  |  | 136 |     animation.on('resume', () => M.util.js_pending('CollapsibleRegion'));
 | 
        
           |  |  | 137 |     animation.on('pause', () => M.util.js_complete('CollapsibleRegion'));
 | 
        
           |  |  | 138 |   | 
        
           | 1 | efrain | 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';
 | 
        
           |  |  | 145 |             } else {
 | 
        
           |  |  | 146 |             collapsedimage = 't/collapsed';
 | 
        
           |  |  | 147 |             }
 | 
        
           |  |  | 148 |         if (this.div.hasClass('collapsed')) {
 | 
        
           |  |  | 149 |             this.icon.set('src', M.util.image_url(collapsedimage, 'moodle'));
 | 
        
           |  |  | 150 |         } else {
 | 
        
           |  |  | 151 |             this.icon.set('src', M.util.image_url('t/expanded', 'moodle'));
 | 
        
           |  |  | 152 |         }
 | 
        
           | 11 | efrain | 153 |   | 
        
           |  |  | 154 |         M.util.js_complete('CollapsibleRegion');
 | 
        
           | 1 | efrain | 155 |     }, this);
 | 
        
           |  |  | 156 |   | 
        
           |  |  | 157 |     // Hook up the event handler.
 | 
        
           |  |  | 158 |     a.on('click', function(e, animation) {
 | 
        
           |  |  | 159 |         e.preventDefault();
 | 
        
           |  |  | 160 |         // Animate to the appropriate size.
 | 
        
           |  |  | 161 |         if (animation.get('running')) {
 | 
        
           |  |  | 162 |             animation.stop();
 | 
        
           |  |  | 163 |         }
 | 
        
           |  |  | 164 |         animation.set('reverse', this.div.hasClass('collapsed'));
 | 
        
           |  |  | 165 |         // Update the user preference.
 | 
        
           |  |  | 166 |         if (this.userpref) {
 | 
        
           |  |  | 167 |             require(['core_user/repository'], function(UserRepository) {
 | 
        
           |  |  | 168 |                 UserRepository.setUserPreference(this.userpref, !this.div.hasClass('collapsed'));
 | 
        
           |  |  | 169 |             }.bind(this));
 | 
        
           |  |  | 170 |         }
 | 
        
           |  |  | 171 |         animation.run();
 | 
        
           |  |  | 172 |     }, this, animation);
 | 
        
           |  |  | 173 | };
 | 
        
           |  |  | 174 |   | 
        
           |  |  | 175 | /**
 | 
        
           |  |  | 176 |  * The user preference that stores the state of this box.
 | 
        
           |  |  | 177 |  * @property userpref
 | 
        
           |  |  | 178 |  * @type String
 | 
        
           |  |  | 179 |  */
 | 
        
           |  |  | 180 | M.util.CollapsibleRegion.prototype.userpref = null;
 | 
        
           |  |  | 181 |   | 
        
           |  |  | 182 | /**
 | 
        
           |  |  | 183 |  * The key divs that make up this
 | 
        
           |  |  | 184 |  * @property div
 | 
        
           |  |  | 185 |  * @type Y.Node
 | 
        
           |  |  | 186 |  */
 | 
        
           |  |  | 187 | M.util.CollapsibleRegion.prototype.div = null;
 | 
        
           |  |  | 188 |   | 
        
           |  |  | 189 | /**
 | 
        
           |  |  | 190 |  * The key divs that make up this
 | 
        
           |  |  | 191 |  * @property icon
 | 
        
           |  |  | 192 |  * @type Y.Node
 | 
        
           |  |  | 193 |  */
 | 
        
           |  |  | 194 | M.util.CollapsibleRegion.prototype.icon = null;
 | 
        
           |  |  | 195 |   | 
        
           |  |  | 196 | /**
 | 
        
           |  |  | 197 |  * @deprecated since Moodle 4.3.
 | 
        
           |  |  | 198 |  */
 | 
        
           | 1441 | ariadna | 199 | M.util.set_user_preference = function() {
 | 
        
           |  |  | 200 |     throw new Error('M.util.set_user_preference is deprecated. Please use the "core_user/repository" module instead.');
 | 
        
           | 1 | efrain | 201 | };
 | 
        
           |  |  | 202 |   | 
        
           |  |  | 203 | /**
 | 
        
           |  |  | 204 |  * Prints a confirmation dialog in the style of DOM.confirm().
 | 
        
           |  |  | 205 |  *
 | 
        
           |  |  | 206 |  * @method show_confirm_dialog
 | 
        
           |  |  | 207 |  * @param {EventFacade} e
 | 
        
           |  |  | 208 |  * @param {Object} args
 | 
        
           |  |  | 209 |  * @param {String} args.message The question to ask the user
 | 
        
           |  |  | 210 |  * @param {Function} [args.callback] A callback to apply on confirmation.
 | 
        
           |  |  | 211 |  * @param {Object} [args.scope] The scope to use when calling the callback.
 | 
        
           |  |  | 212 |  * @param {Object} [args.callbackargs] Any arguments to pass to the callback.
 | 
        
           |  |  | 213 |  * @param {String} [args.cancellabel] The label to use on the cancel button.
 | 
        
           |  |  | 214 |  * @param {String} [args.continuelabel] The label to use on the continue button.
 | 
        
           |  |  | 215 |  */
 | 
        
           |  |  | 216 | M.util.show_confirm_dialog = (e, {
 | 
        
           |  |  | 217 |     message,
 | 
        
           |  |  | 218 |     continuelabel,
 | 
        
           |  |  | 219 |     callback = null,
 | 
        
           |  |  | 220 |     scope = null,
 | 
        
           |  |  | 221 |     callbackargs = [],
 | 
        
           |  |  | 222 | } = {}) => {
 | 
        
           |  |  | 223 |     if (e.preventDefault) {
 | 
        
           |  |  | 224 |         e.preventDefault();
 | 
        
           |  |  | 225 |     }
 | 
        
           |  |  | 226 |   | 
        
           |  |  | 227 |     require(
 | 
        
           |  |  | 228 |         ['core/notification', 'core/str', 'core_form/changechecker', 'core/normalise'],
 | 
        
           |  |  | 229 |         function(Notification, Str, FormChangeChecker, Normalise) {
 | 
        
           |  |  | 230 |   | 
        
           |  |  | 231 |             if (scope === null && e.target) {
 | 
        
           |  |  | 232 |                 // Fall back to the event target if no scope provided.
 | 
        
           |  |  | 233 |                 scope = e.target;
 | 
        
           |  |  | 234 |             }
 | 
        
           |  |  | 235 |   | 
        
           |  |  | 236 |             Notification.saveCancelPromise(
 | 
        
           |  |  | 237 |                 Str.get_string('confirmation', 'admin'),
 | 
        
           |  |  | 238 |                 message,
 | 
        
           |  |  | 239 |                 continuelabel || Str.get_string('yes', 'moodle'),
 | 
        
           |  |  | 240 |             )
 | 
        
           |  |  | 241 |             .then(() => {
 | 
        
           |  |  | 242 |                 if (callback) {
 | 
        
           |  |  | 243 |                     callback.apply(scope, callbackargs);
 | 
        
           |  |  | 244 |                     return;
 | 
        
           |  |  | 245 |                 }
 | 
        
           |  |  | 246 |   | 
        
           |  |  | 247 |                 if (!e.target) {
 | 
        
           |  |  | 248 |                     window.console.error(
 | 
        
           |  |  | 249 |                         `M.util.show_confirm_dialog: No target found for event`,
 | 
        
           |  |  | 250 |                         e
 | 
        
           |  |  | 251 |                     );
 | 
        
           |  |  | 252 |                     return;
 | 
        
           |  |  | 253 |                 }
 | 
        
           |  |  | 254 |   | 
        
           |  |  | 255 |                 const target = Normalise.getElement(e.target);
 | 
        
           |  |  | 256 |   | 
        
           |  |  | 257 |                 if (target.closest('a')) {
 | 
        
           |  |  | 258 |                     window.location = target.closest('a').getAttribute('href');
 | 
        
           |  |  | 259 |                     return;
 | 
        
           |  |  | 260 |                 } else if (target.closest('input') || target.closest('button')) {
 | 
        
           |  |  | 261 |                     const form = target.closest('form');
 | 
        
           |  |  | 262 |                     const hiddenValue = document.createElement('input');
 | 
        
           |  |  | 263 |                     hiddenValue.setAttribute('type', 'hidden');
 | 
        
           |  |  | 264 |                     hiddenValue.setAttribute('name', target.getAttribute('name'));
 | 
        
           |  |  | 265 |                     hiddenValue.setAttribute('value', target.getAttribute('value'));
 | 
        
           |  |  | 266 |                     form.appendChild(hiddenValue);
 | 
        
           |  |  | 267 |                     FormChangeChecker.markFormAsDirty(form);
 | 
        
           |  |  | 268 |                     form.submit();
 | 
        
           |  |  | 269 |                     return;
 | 
        
           |  |  | 270 |                 } else if (target.closest('form')) {
 | 
        
           |  |  | 271 |                     const form = target.closest('form');
 | 
        
           |  |  | 272 |                     FormChangeChecker.markFormAsDirty(form);
 | 
        
           |  |  | 273 |                     form.submit();
 | 
        
           |  |  | 274 |                     return;
 | 
        
           |  |  | 275 |                 }
 | 
        
           |  |  | 276 |                 window.console.error(
 | 
        
           |  |  | 277 |                     `Element of type ${target.tagName} is not supported by M.util.show_confirm_dialog.`
 | 
        
           |  |  | 278 |                 );
 | 
        
           |  |  | 279 |   | 
        
           |  |  | 280 |                 return;
 | 
        
           |  |  | 281 |             })
 | 
        
           |  |  | 282 |             .catch(() => {
 | 
        
           |  |  | 283 |                 // User cancelled.
 | 
        
           |  |  | 284 |                 return;
 | 
        
           |  |  | 285 |             });
 | 
        
           |  |  | 286 |         }
 | 
        
           |  |  | 287 |     );
 | 
        
           |  |  | 288 | };
 | 
        
           |  |  | 289 |   | 
        
           |  |  | 290 | /** Useful for full embedding of various stuff */
 | 
        
           |  |  | 291 | M.util.init_maximised_embed = function(Y, id) {
 | 
        
           |  |  | 292 |     var obj = Y.one('#'+id);
 | 
        
           |  |  | 293 |     if (!obj) {
 | 
        
           |  |  | 294 |         return;
 | 
        
           |  |  | 295 |     }
 | 
        
           |  |  | 296 |   | 
        
           |  |  | 297 |     var get_htmlelement_size = function(el, prop) {
 | 
        
           |  |  | 298 |         if (Y.Lang.isString(el)) {
 | 
        
           |  |  | 299 |             el = Y.one('#' + el);
 | 
        
           |  |  | 300 |         }
 | 
        
           |  |  | 301 |         // Ensure element exists.
 | 
        
           |  |  | 302 |         if (el) {
 | 
        
           |  |  | 303 |             var val = el.getStyle(prop);
 | 
        
           |  |  | 304 |             if (val == 'auto') {
 | 
        
           |  |  | 305 |                 val = el.getComputedStyle(prop);
 | 
        
           |  |  | 306 |             }
 | 
        
           |  |  | 307 |             val = parseInt(val);
 | 
        
           |  |  | 308 |             if (isNaN(val)) {
 | 
        
           |  |  | 309 |                 return 0;
 | 
        
           |  |  | 310 |             }
 | 
        
           |  |  | 311 |             return val;
 | 
        
           |  |  | 312 |         } else {
 | 
        
           |  |  | 313 |             return 0;
 | 
        
           |  |  | 314 |         }
 | 
        
           |  |  | 315 |     };
 | 
        
           |  |  | 316 |   | 
        
           |  |  | 317 |     var resize_object = function() {
 | 
        
           |  |  | 318 |         obj.setStyle('display', 'none');
 | 
        
           |  |  | 319 |         var newwidth = get_htmlelement_size('maincontent', 'width') - 35;
 | 
        
           |  |  | 320 |   | 
        
           |  |  | 321 |         if (newwidth > 500) {
 | 
        
           |  |  | 322 |             obj.setStyle('width', newwidth  + 'px');
 | 
        
           |  |  | 323 |         } else {
 | 
        
           |  |  | 324 |             obj.setStyle('width', '500px');
 | 
        
           |  |  | 325 |         }
 | 
        
           |  |  | 326 |   | 
        
           |  |  | 327 |         var headerheight = get_htmlelement_size('page-header', 'height');
 | 
        
           |  |  | 328 |         var footerheight = get_htmlelement_size('page-footer', 'height');
 | 
        
           |  |  | 329 |         var newheight = parseInt(Y.one('body').get('docHeight')) - footerheight - headerheight - 100;
 | 
        
           |  |  | 330 |         if (newheight < 400) {
 | 
        
           |  |  | 331 |             newheight = 400;
 | 
        
           |  |  | 332 |         }
 | 
        
           |  |  | 333 |         obj.setStyle('height', newheight+'px');
 | 
        
           |  |  | 334 |         obj.setStyle('display', '');
 | 
        
           |  |  | 335 |     };
 | 
        
           |  |  | 336 |   | 
        
           |  |  | 337 |     resize_object();
 | 
        
           |  |  | 338 |     // fix layout if window resized too
 | 
        
           |  |  | 339 |     Y.use('event-resize', function (Y) {
 | 
        
           |  |  | 340 |         Y.on("windowresize", function() {
 | 
        
           |  |  | 341 |             resize_object();
 | 
        
           |  |  | 342 |         });
 | 
        
           |  |  | 343 |     });
 | 
        
           |  |  | 344 | };
 | 
        
           |  |  | 345 |   | 
        
           |  |  | 346 | /**
 | 
        
           |  |  | 347 |  * Breaks out all links to the top frame - used in frametop page layout.
 | 
        
           |  |  | 348 |  */
 | 
        
           |  |  | 349 | M.util.init_frametop = function(Y) {
 | 
        
           |  |  | 350 |     Y.all('a').each(function(node) {
 | 
        
           |  |  | 351 |         node.set('target', '_top');
 | 
        
           |  |  | 352 |     });
 | 
        
           |  |  | 353 |     Y.all('form').each(function(node) {
 | 
        
           |  |  | 354 |         node.set('target', '_top');
 | 
        
           |  |  | 355 |     });
 | 
        
           |  |  | 356 | };
 | 
        
           |  |  | 357 |   | 
        
           |  |  | 358 | /**
 | 
        
           |  |  | 359 |  * @deprecated since Moodle 3.3
 | 
        
           |  |  | 360 |  */
 | 
        
           |  |  | 361 | M.util.init_toggle_class_on_click = function(Y, id, cssselector, toggleclassname, togglecssselector) {
 | 
        
           |  |  | 362 |     throw new Error('M.util.init_toggle_class_on_click can not be used any more. Please use jQuery instead.');
 | 
        
           |  |  | 363 | };
 | 
        
           |  |  | 364 |   | 
        
           |  |  | 365 | /**
 | 
        
           |  |  | 366 |  * Initialises a colour picker
 | 
        
           |  |  | 367 |  *
 | 
        
           |  |  | 368 |  * Designed to be used with admin_setting_configcolourpicker although could be used
 | 
        
           |  |  | 369 |  * anywhere, just give a text input an id and insert a div with the class admin_colourpicker
 | 
        
           |  |  | 370 |  * above or below the input (must have the same parent) and then call this with the
 | 
        
           |  |  | 371 |  * id.
 | 
        
           |  |  | 372 |  *
 | 
        
           |  |  | 373 |  * This code was mostly taken from my [Sam Hemelryk] css theme tool available in
 | 
        
           |  |  | 374 |  * contrib/blocks. For better docs refer to that.
 | 
        
           |  |  | 375 |  *
 | 
        
           |  |  | 376 |  * @param {YUI} Y
 | 
        
           |  |  | 377 |  * @param {int} id
 | 
        
           |  |  | 378 |  * @param {object} previewconf
 | 
        
           |  |  | 379 |  */
 | 
        
           |  |  | 380 | M.util.init_colour_picker = function(Y, id, previewconf) {
 | 
        
           |  |  | 381 |     /**
 | 
        
           |  |  | 382 |      * We need node and event-mouseenter
 | 
        
           |  |  | 383 |      */
 | 
        
           |  |  | 384 |     Y.use('node', 'event-mouseenter', function(){
 | 
        
           |  |  | 385 |         /**
 | 
        
           |  |  | 386 |          * The colour picker object
 | 
        
           |  |  | 387 |          */
 | 
        
           |  |  | 388 |         var colourpicker = {
 | 
        
           |  |  | 389 |             box : null,
 | 
        
           |  |  | 390 |             input : null,
 | 
        
           |  |  | 391 |             image : null,
 | 
        
           |  |  | 392 |             preview : null,
 | 
        
           |  |  | 393 |             current : null,
 | 
        
           |  |  | 394 |             eventClick : null,
 | 
        
           |  |  | 395 |             eventMouseEnter : null,
 | 
        
           |  |  | 396 |             eventMouseLeave : null,
 | 
        
           |  |  | 397 |             eventMouseMove : null,
 | 
        
           |  |  | 398 |             width : 300,
 | 
        
           |  |  | 399 |             height :  100,
 | 
        
           |  |  | 400 |             factor : 5,
 | 
        
           |  |  | 401 |             /**
 | 
        
           |  |  | 402 |              * Initalises the colour picker by putting everything together and wiring the events
 | 
        
           |  |  | 403 |              */
 | 
        
           |  |  | 404 |             init : function() {
 | 
        
           |  |  | 405 |                 this.input = Y.one('#'+id);
 | 
        
           |  |  | 406 |                 this.box = this.input.ancestor().one('.admin_colourpicker');
 | 
        
           |  |  | 407 |                 this.image = Y.Node.create('<img alt="" class="colourdialogue" />');
 | 
        
           |  |  | 408 |                 this.image.setAttribute('src', M.util.image_url('i/colourpicker', 'moodle'));
 | 
        
           |  |  | 409 |                 this.preview = Y.Node.create('<div class="previewcolour"></div>');
 | 
        
           |  |  | 410 |                 this.preview.setStyle('width', this.height/2).setStyle('height', this.height/2).setStyle('backgroundColor', this.input.get('value'));
 | 
        
           |  |  | 411 |                 this.current = Y.Node.create('<div class="currentcolour"></div>');
 | 
        
           |  |  | 412 |                 this.current.setStyle('width', this.height/2).setStyle('height', this.height/2 -1).setStyle('backgroundColor', this.input.get('value'));
 | 
        
           |  |  | 413 |                 this.box.setContent('').append(this.image).append(this.preview).append(this.current);
 | 
        
           |  |  | 414 |   | 
        
           |  |  | 415 |                 if (typeof(previewconf) === 'object' && previewconf !== null) {
 | 
        
           |  |  | 416 |                     Y.one('#'+id+'_preview').on('click', function(e){
 | 
        
           |  |  | 417 |                         if (Y.Lang.isString(previewconf.selector)) {
 | 
        
           |  |  | 418 |                             Y.all(previewconf.selector).setStyle(previewconf.style, this.input.get('value'));
 | 
        
           |  |  | 419 |                         } else {
 | 
        
           |  |  | 420 |                             for (var i in previewconf.selector) {
 | 
        
           |  |  | 421 |                                 Y.all(previewconf.selector[i]).setStyle(previewconf.style, this.input.get('value'));
 | 
        
           |  |  | 422 |                             }
 | 
        
           |  |  | 423 |                         }
 | 
        
           |  |  | 424 |                     }, this);
 | 
        
           |  |  | 425 |                 }
 | 
        
           |  |  | 426 |   | 
        
           |  |  | 427 |                 this.eventClick = this.image.on('click', this.pickColour, this);
 | 
        
           |  |  | 428 |                 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
 | 
        
           |  |  | 429 |             },
 | 
        
           |  |  | 430 |             /**
 | 
        
           |  |  | 431 |              * Starts to follow the mouse once it enter the image
 | 
        
           |  |  | 432 |              */
 | 
        
           |  |  | 433 |             startFollow : function(e) {
 | 
        
           |  |  | 434 |                 this.eventMouseEnter.detach();
 | 
        
           |  |  | 435 |                 this.eventMouseLeave = Y.on('mouseleave', this.endFollow, this.image, this);
 | 
        
           |  |  | 436 |                 this.eventMouseMove = this.image.on('mousemove', function(e){
 | 
        
           |  |  | 437 |                     this.preview.setStyle('backgroundColor', this.determineColour(e));
 | 
        
           |  |  | 438 |                 }, this);
 | 
        
           |  |  | 439 |             },
 | 
        
           |  |  | 440 |             /**
 | 
        
           |  |  | 441 |              * Stops following the mouse
 | 
        
           |  |  | 442 |              */
 | 
        
           |  |  | 443 |             endFollow : function(e) {
 | 
        
           |  |  | 444 |                 this.eventMouseMove.detach();
 | 
        
           |  |  | 445 |                 this.eventMouseLeave.detach();
 | 
        
           |  |  | 446 |                 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
 | 
        
           |  |  | 447 |             },
 | 
        
           |  |  | 448 |             /**
 | 
        
           |  |  | 449 |              * Picks the colour the was clicked on
 | 
        
           |  |  | 450 |              */
 | 
        
           |  |  | 451 |             pickColour : function(e) {
 | 
        
           |  |  | 452 |                 var colour = this.determineColour(e);
 | 
        
           |  |  | 453 |                 this.input.set('value', colour);
 | 
        
           |  |  | 454 |                 this.current.setStyle('backgroundColor', colour);
 | 
        
           |  |  | 455 |             },
 | 
        
           |  |  | 456 |             /**
 | 
        
           |  |  | 457 |              * Calculates the colour fromthe given co-ordinates
 | 
        
           |  |  | 458 |              */
 | 
        
           |  |  | 459 |             determineColour : function(e) {
 | 
        
           |  |  | 460 |                 var eventx = Math.floor(e.pageX-e.target.getX());
 | 
        
           |  |  | 461 |                 var eventy = Math.floor(e.pageY-e.target.getY());
 | 
        
           |  |  | 462 |   | 
        
           |  |  | 463 |                 var imagewidth = this.width;
 | 
        
           |  |  | 464 |                 var imageheight = this.height;
 | 
        
           |  |  | 465 |                 var factor = this.factor;
 | 
        
           |  |  | 466 |                 var colour = [255,0,0];
 | 
        
           |  |  | 467 |   | 
        
           |  |  | 468 |                 var matrices = [
 | 
        
           |  |  | 469 |                     [  0,  1,  0],
 | 
        
           |  |  | 470 |                     [ -1,  0,  0],
 | 
        
           |  |  | 471 |                     [  0,  0,  1],
 | 
        
           |  |  | 472 |                     [  0, -1,  0],
 | 
        
           |  |  | 473 |                     [  1,  0,  0],
 | 
        
           |  |  | 474 |                     [  0,  0, -1]
 | 
        
           |  |  | 475 |                 ];
 | 
        
           |  |  | 476 |   | 
        
           |  |  | 477 |                 var matrixcount = matrices.length;
 | 
        
           |  |  | 478 |                 var limit = Math.round(imagewidth/matrixcount);
 | 
        
           |  |  | 479 |                 var heightbreak = Math.round(imageheight/2);
 | 
        
           |  |  | 480 |   | 
        
           |  |  | 481 |                 for (var x = 0; x < imagewidth; x++) {
 | 
        
           |  |  | 482 |                     var divisor = Math.floor(x / limit);
 | 
        
           |  |  | 483 |                     var matrix = matrices[divisor];
 | 
        
           |  |  | 484 |   | 
        
           |  |  | 485 |                     colour[0] += matrix[0]*factor;
 | 
        
           |  |  | 486 |                     colour[1] += matrix[1]*factor;
 | 
        
           |  |  | 487 |                     colour[2] += matrix[2]*factor;
 | 
        
           |  |  | 488 |   | 
        
           |  |  | 489 |                     if (eventx==x) {
 | 
        
           |  |  | 490 |                         break;
 | 
        
           |  |  | 491 |                     }
 | 
        
           |  |  | 492 |                 }
 | 
        
           |  |  | 493 |   | 
        
           |  |  | 494 |                 var pixel = [colour[0], colour[1], colour[2]];
 | 
        
           |  |  | 495 |                 if (eventy < heightbreak) {
 | 
        
           |  |  | 496 |                     pixel[0] += Math.floor(((255-pixel[0])/heightbreak) * (heightbreak - eventy));
 | 
        
           |  |  | 497 |                     pixel[1] += Math.floor(((255-pixel[1])/heightbreak) * (heightbreak - eventy));
 | 
        
           |  |  | 498 |                     pixel[2] += Math.floor(((255-pixel[2])/heightbreak) * (heightbreak - eventy));
 | 
        
           |  |  | 499 |                 } else if (eventy > heightbreak) {
 | 
        
           |  |  | 500 |                     pixel[0] = Math.floor((imageheight-eventy)*(pixel[0]/heightbreak));
 | 
        
           |  |  | 501 |                     pixel[1] = Math.floor((imageheight-eventy)*(pixel[1]/heightbreak));
 | 
        
           |  |  | 502 |                     pixel[2] = Math.floor((imageheight-eventy)*(pixel[2]/heightbreak));
 | 
        
           |  |  | 503 |                 }
 | 
        
           |  |  | 504 |   | 
        
           |  |  | 505 |                 return this.convert_rgb_to_hex(pixel);
 | 
        
           |  |  | 506 |             },
 | 
        
           |  |  | 507 |             /**
 | 
        
           |  |  | 508 |              * Converts an RGB value to Hex
 | 
        
           |  |  | 509 |              */
 | 
        
           |  |  | 510 |             convert_rgb_to_hex : function(rgb) {
 | 
        
           |  |  | 511 |                 var hex = '#';
 | 
        
           |  |  | 512 |                 var hexchars = "0123456789ABCDEF";
 | 
        
           |  |  | 513 |                 for (var i=0; i<3; i++) {
 | 
        
           |  |  | 514 |                     var number = Math.abs(rgb[i]);
 | 
        
           |  |  | 515 |                     if (number == 0 || isNaN(number)) {
 | 
        
           |  |  | 516 |                         hex += '00';
 | 
        
           |  |  | 517 |                     } else {
 | 
        
           |  |  | 518 |                         hex += hexchars.charAt((number-number%16)/16)+hexchars.charAt(number%16);
 | 
        
           |  |  | 519 |                     }
 | 
        
           |  |  | 520 |                 }
 | 
        
           |  |  | 521 |                 return hex;
 | 
        
           |  |  | 522 |             }
 | 
        
           |  |  | 523 |         };
 | 
        
           |  |  | 524 |         /**
 | 
        
           |  |  | 525 |          * Initialise the colour picker :) Hoorah
 | 
        
           |  |  | 526 |          */
 | 
        
           |  |  | 527 |         colourpicker.init();
 | 
        
           |  |  | 528 |     });
 | 
        
           |  |  | 529 | };
 | 
        
           |  |  | 530 |   | 
        
           |  |  | 531 | M.util.init_block_hider = function(Y, config) {
 | 
        
           |  |  | 532 |     Y.use('base', 'node', function(Y) {
 | 
        
           |  |  | 533 |         M.util.block_hider = M.util.block_hider || (function(){
 | 
        
           |  |  | 534 |             var blockhider = function() {
 | 
        
           |  |  | 535 |                 blockhider.superclass.constructor.apply(this, arguments);
 | 
        
           |  |  | 536 |             };
 | 
        
           |  |  | 537 |             blockhider.prototype = {
 | 
        
           |  |  | 538 |                 initializer : function(config) {
 | 
        
           |  |  | 539 |                     this.set('block', '#'+this.get('id'));
 | 
        
           |  |  | 540 |                     var b = this.get('block'),
 | 
        
           |  |  | 541 |                         t = b.one('.title'),
 | 
        
           |  |  | 542 |                         a = null,
 | 
        
           |  |  | 543 |                         hide,
 | 
        
           |  |  | 544 |                         show;
 | 
        
           |  |  | 545 |                     if (t && (a = t.one('.block_action'))) {
 | 
        
           |  |  | 546 |                         hide = Y.Node.create('<img />')
 | 
        
           |  |  | 547 |                             .addClass('block-hider-hide')
 | 
        
           |  |  | 548 |                             .setAttrs({
 | 
        
           |  |  | 549 |                                 alt:        config.tooltipVisible,
 | 
        
           |  |  | 550 |                                 src:        this.get('iconVisible'),
 | 
        
           |  |  | 551 |                                 tabIndex:   0,
 | 
        
           |  |  | 552 |                                 'title':    config.tooltipVisible
 | 
        
           |  |  | 553 |                             });
 | 
        
           |  |  | 554 |                         hide.on('keypress', this.updateStateKey, this, true);
 | 
        
           |  |  | 555 |                         hide.on('click', this.updateState, this, true);
 | 
        
           |  |  | 556 |   | 
        
           |  |  | 557 |                         show = Y.Node.create('<img />')
 | 
        
           |  |  | 558 |                             .addClass('block-hider-show')
 | 
        
           |  |  | 559 |                             .setAttrs({
 | 
        
           |  |  | 560 |                                 alt:        config.tooltipHidden,
 | 
        
           |  |  | 561 |                                 src:        this.get('iconHidden'),
 | 
        
           |  |  | 562 |                                 tabIndex:   0,
 | 
        
           |  |  | 563 |                                 'title':    config.tooltipHidden
 | 
        
           |  |  | 564 |                             });
 | 
        
           |  |  | 565 |                         show.on('keypress', this.updateStateKey, this, false);
 | 
        
           |  |  | 566 |                         show.on('click', this.updateState, this, false);
 | 
        
           |  |  | 567 |   | 
        
           |  |  | 568 |                         a.insert(show, 0).insert(hide, 0);
 | 
        
           |  |  | 569 |                     }
 | 
        
           |  |  | 570 |                 },
 | 
        
           |  |  | 571 |                 updateState : function(e, hide) {
 | 
        
           |  |  | 572 |                     require(['core_user/repository'], function(UserRepository) {
 | 
        
           |  |  | 573 |                         UserRepository.setUserPreference(this.get('preference'), hide);
 | 
        
           |  |  | 574 |                     }.bind(this));
 | 
        
           |  |  | 575 |                     if (hide) {
 | 
        
           |  |  | 576 |                         this.get('block').addClass('hidden');
 | 
        
           |  |  | 577 |                         this.get('block').one('.block-hider-show').focus();
 | 
        
           |  |  | 578 |                     } else {
 | 
        
           |  |  | 579 |                         this.get('block').removeClass('hidden');
 | 
        
           |  |  | 580 |                         this.get('block').one('.block-hider-hide').focus();
 | 
        
           |  |  | 581 |                     }
 | 
        
           |  |  | 582 |                 },
 | 
        
           |  |  | 583 |                 updateStateKey : function(e, hide) {
 | 
        
           |  |  | 584 |                     if (e.keyCode == 13) { //allow hide/show via enter key
 | 
        
           |  |  | 585 |                         this.updateState(this, hide);
 | 
        
           |  |  | 586 |                     }
 | 
        
           |  |  | 587 |                 }
 | 
        
           |  |  | 588 |             };
 | 
        
           |  |  | 589 |             Y.extend(blockhider, Y.Base, blockhider.prototype, {
 | 
        
           |  |  | 590 |                 NAME : 'blockhider',
 | 
        
           |  |  | 591 |                 ATTRS : {
 | 
        
           |  |  | 592 |                     id : {},
 | 
        
           |  |  | 593 |                     preference : {},
 | 
        
           |  |  | 594 |                     iconVisible : {
 | 
        
           |  |  | 595 |                         value : M.util.image_url('t/switch_minus', 'moodle')
 | 
        
           |  |  | 596 |                     },
 | 
        
           |  |  | 597 |                     iconHidden : {
 | 
        
           |  |  | 598 |                         value : M.util.image_url('t/switch_plus', 'moodle')
 | 
        
           |  |  | 599 |                     },
 | 
        
           |  |  | 600 |                     block : {
 | 
        
           |  |  | 601 |                         setter : function(node) {
 | 
        
           |  |  | 602 |                             return Y.one(node);
 | 
        
           |  |  | 603 |                         }
 | 
        
           |  |  | 604 |                     }
 | 
        
           |  |  | 605 |                 }
 | 
        
           |  |  | 606 |             });
 | 
        
           |  |  | 607 |             return blockhider;
 | 
        
           |  |  | 608 |         })();
 | 
        
           |  |  | 609 |         new M.util.block_hider(config);
 | 
        
           |  |  | 610 |     });
 | 
        
           |  |  | 611 | };
 | 
        
           |  |  | 612 |   | 
        
           |  |  | 613 | /**
 | 
        
           |  |  | 614 |  * @var pending_js - The keys are the list of all pending js actions.
 | 
        
           |  |  | 615 |  * @type Object
 | 
        
           |  |  | 616 |  */
 | 
        
           |  |  | 617 | M.util.pending_js = [];
 | 
        
           |  |  | 618 | M.util.complete_js = [];
 | 
        
           |  |  | 619 |   | 
        
           |  |  | 620 | /**
 | 
        
           |  |  | 621 |  * Register any long running javascript code with a unique identifier.
 | 
        
           |  |  | 622 |  * This is used to ensure that Behat steps do not continue with interactions until the page finishes loading.
 | 
        
           |  |  | 623 |  *
 | 
        
           |  |  | 624 |  * All calls to M.util.js_pending _must_ be followed by a subsequent call to M.util.js_complete with the same exact
 | 
        
           |  |  | 625 |  * uniqid.
 | 
        
           |  |  | 626 |  *
 | 
        
           |  |  | 627 |  * This function may also be called with no arguments to test if there is any js calls pending.
 | 
        
           |  |  | 628 |  *
 | 
        
           |  |  | 629 |  * The uniqid specified may be any Object, including Number, String, or actual Object; however please note that the
 | 
        
           |  |  | 630 |  * paired js_complete function performs a strict search for the key specified. As such, if using an Object, the exact
 | 
        
           |  |  | 631 |  * Object must be passed into both functions.
 | 
        
           |  |  | 632 |  *
 | 
        
           |  |  | 633 |  * @param   {Mixed}     uniqid Register long-running code against the supplied identifier
 | 
        
           |  |  | 634 |  * @return  {Number}    Number of pending items
 | 
        
           |  |  | 635 |  */
 | 
        
           |  |  | 636 | M.util.js_pending = function(uniqid) {
 | 
        
           |  |  | 637 |     if (typeof uniqid !== 'undefined') {
 | 
        
           |  |  | 638 |         M.util.pending_js.push(uniqid);
 | 
        
           |  |  | 639 |     }
 | 
        
           |  |  | 640 |   | 
        
           |  |  | 641 |     return M.util.pending_js.length;
 | 
        
           |  |  | 642 | };
 | 
        
           |  |  | 643 |   | 
        
           |  |  | 644 | // Start this asap.
 | 
        
           |  |  | 645 | M.util.js_pending('init');
 | 
        
           |  |  | 646 |   | 
        
           |  |  | 647 | /**
 | 
        
           |  |  | 648 |  * Register listeners for Y.io start/end so we can wait for them in behat.
 | 
        
           |  |  | 649 |  */
 | 
        
           |  |  | 650 | YUI.add('moodle-core-io', function(Y) {
 | 
        
           |  |  | 651 |     Y.on('io:start', function(id) {
 | 
        
           |  |  | 652 |         M.util.js_pending('io:' + id);
 | 
        
           |  |  | 653 |     });
 | 
        
           |  |  | 654 |     Y.on('io:end', function(id) {
 | 
        
           |  |  | 655 |         M.util.js_complete('io:' + id);
 | 
        
           |  |  | 656 |     });
 | 
        
           |  |  | 657 | }, '@VERSION@', {
 | 
        
           |  |  | 658 |     condition: {
 | 
        
           |  |  | 659 |         trigger: 'io-base',
 | 
        
           |  |  | 660 |         when: 'after'
 | 
        
           |  |  | 661 |     }
 | 
        
           |  |  | 662 | });
 | 
        
           |  |  | 663 |   | 
        
           |  |  | 664 | /**
 | 
        
           |  |  | 665 |  * Unregister some long running javascript code using the unique identifier specified in M.util.js_pending.
 | 
        
           |  |  | 666 |  *
 | 
        
           |  |  | 667 |  * This function must be matched with an identical call to M.util.js_pending.
 | 
        
           |  |  | 668 |  *
 | 
        
           |  |  | 669 |  * @param   {Mixed}     uniqid Register long-running code against the supplied identifier
 | 
        
           |  |  | 670 |  * @return  {Number}    Number of pending items remaining after removing this item
 | 
        
           |  |  | 671 |  */
 | 
        
           |  |  | 672 | M.util.js_complete = function(uniqid) {
 | 
        
           |  |  | 673 |     const index = M.util.pending_js.indexOf(uniqid);
 | 
        
           |  |  | 674 |     if (index >= 0) {
 | 
        
           |  |  | 675 |         M.util.complete_js.push(M.util.pending_js.splice(index, 1)[0]);
 | 
        
           |  |  | 676 |     } else {
 | 
        
           |  |  | 677 |         window.console.log("Unable to locate key for js_complete call", uniqid);
 | 
        
           |  |  | 678 |     }
 | 
        
           |  |  | 679 |   | 
        
           |  |  | 680 |     return M.util.pending_js.length;
 | 
        
           |  |  | 681 | };
 | 
        
           |  |  | 682 |   | 
        
           |  |  | 683 | /**
 | 
        
           |  |  | 684 |  * Returns a string registered in advance for usage in JavaScript
 | 
        
           |  |  | 685 |  *
 | 
        
           |  |  | 686 |  * If you do not pass the third parameter, the function will just return
 | 
        
           |  |  | 687 |  * the corresponding value from the M.str object. If the third parameter is
 | 
        
           |  |  | 688 |  * provided, the function performs {$a} placeholder substitution in the
 | 
        
           |  |  | 689 |  * same way as PHP get_string() in Moodle does.
 | 
        
           |  |  | 690 |  *
 | 
        
           |  |  | 691 |  * @param {String} identifier string identifier
 | 
        
           |  |  | 692 |  * @param {String} component the component providing the string
 | 
        
           |  |  | 693 |  * @param {Object|String} [a] optional variable to populate placeholder with
 | 
        
           |  |  | 694 |  */
 | 
        
           |  |  | 695 | M.util.get_string = function(identifier, component, a) {
 | 
        
           |  |  | 696 |     var stringvalue;
 | 
        
           |  |  | 697 |   | 
        
           |  |  | 698 |     if (M.cfg.developerdebug) {
 | 
        
           |  |  | 699 |         // creating new instance if YUI is not optimal but it seems to be better way then
 | 
        
           |  |  | 700 |         // require the instance via the function API - note that it is used in rare cases
 | 
        
           |  |  | 701 |         // for debugging only anyway
 | 
        
           |  |  | 702 |         // To ensure we don't kill browser performance if hundreds of get_string requests
 | 
        
           |  |  | 703 |         // are made we cache the instance we generate within the M.util namespace.
 | 
        
           |  |  | 704 |         // We don't publicly define the variable so that it doesn't get abused.
 | 
        
           |  |  | 705 |         if (typeof M.util.get_string_yui_instance === 'undefined') {
 | 
        
           |  |  | 706 |             M.util.get_string_yui_instance = new YUI({ debug : true });
 | 
        
           |  |  | 707 |         }
 | 
        
           |  |  | 708 |         var Y = M.util.get_string_yui_instance;
 | 
        
           |  |  | 709 |     }
 | 
        
           |  |  | 710 |   | 
        
           |  |  | 711 |     if (!M.str.hasOwnProperty(component) || !M.str[component].hasOwnProperty(identifier)) {
 | 
        
           |  |  | 712 |         stringvalue = '[[' + identifier + ',' + component + ']]';
 | 
        
           |  |  | 713 |         if (M.cfg.developerdebug) {
 | 
        
           |  |  | 714 |             Y.log('undefined string ' + stringvalue, 'warn', 'M.util.get_string');
 | 
        
           |  |  | 715 |         }
 | 
        
           |  |  | 716 |         return stringvalue;
 | 
        
           |  |  | 717 |     }
 | 
        
           |  |  | 718 |   | 
        
           |  |  | 719 |     stringvalue = M.str[component][identifier];
 | 
        
           |  |  | 720 |   | 
        
           |  |  | 721 |     if (typeof a == 'undefined') {
 | 
        
           |  |  | 722 |         // no placeholder substitution requested
 | 
        
           |  |  | 723 |         return stringvalue;
 | 
        
           |  |  | 724 |     }
 | 
        
           |  |  | 725 |   | 
        
           |  |  | 726 |     if (typeof a == 'number' || typeof a == 'string') {
 | 
        
           |  |  | 727 |         // replace all occurrences of {$a} with the placeholder value
 | 
        
           |  |  | 728 |         stringvalue = stringvalue.replace(/\{\$a\}/g, a);
 | 
        
           |  |  | 729 |         return stringvalue;
 | 
        
           |  |  | 730 |     }
 | 
        
           |  |  | 731 |   | 
        
           |  |  | 732 |     if (typeof a == 'object') {
 | 
        
           |  |  | 733 |         // replace {$a->key} placeholders
 | 
        
           |  |  | 734 |         for (var key in a) {
 | 
        
           |  |  | 735 |             if (typeof a[key] != 'number' && typeof a[key] != 'string') {
 | 
        
           |  |  | 736 |                 if (M.cfg.developerdebug) {
 | 
        
           |  |  | 737 |                     Y.log('invalid value type for $a->' + key, 'warn', 'M.util.get_string');
 | 
        
           |  |  | 738 |                 }
 | 
        
           |  |  | 739 |                 continue;
 | 
        
           |  |  | 740 |             }
 | 
        
           |  |  | 741 |             var search = '{$a->' + key + '}';
 | 
        
           |  |  | 742 |             search = search.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
 | 
        
           |  |  | 743 |             search = new RegExp(search, 'g');
 | 
        
           |  |  | 744 |             stringvalue = stringvalue.replace(search, a[key]);
 | 
        
           |  |  | 745 |         }
 | 
        
           |  |  | 746 |         return stringvalue;
 | 
        
           |  |  | 747 |     }
 | 
        
           |  |  | 748 |   | 
        
           |  |  | 749 |     if (M.cfg.developerdebug) {
 | 
        
           |  |  | 750 |         Y.log('incorrect placeholder type', 'warn', 'M.util.get_string');
 | 
        
           |  |  | 751 |     }
 | 
        
           |  |  | 752 |     return stringvalue;
 | 
        
           |  |  | 753 | };
 | 
        
           |  |  | 754 |   | 
        
           |  |  | 755 | /**
 | 
        
           |  |  | 756 |  * Set focus on username or password field of the login form.
 | 
        
           |  |  | 757 |  * @deprecated since Moodle 3.3.
 | 
        
           |  |  | 758 |  */
 | 
        
           |  |  | 759 | M.util.focus_login_form = function(Y) {
 | 
        
           |  |  | 760 |     Y.log('M.util.focus_login_form no longer does anything. Please use jquery instead.', 'warn', 'javascript-static.js');
 | 
        
           |  |  | 761 | };
 | 
        
           |  |  | 762 |   | 
        
           |  |  | 763 | /**
 | 
        
           |  |  | 764 |  * Set focus on login error message.
 | 
        
           |  |  | 765 |  * @deprecated since Moodle 3.3.
 | 
        
           |  |  | 766 |  */
 | 
        
           |  |  | 767 | M.util.focus_login_error = function(Y) {
 | 
        
           |  |  | 768 |     Y.log('M.util.focus_login_error no longer does anything. Please use jquery instead.', 'warn', 'javascript-static.js');
 | 
        
           |  |  | 769 | };
 | 
        
           |  |  | 770 |   | 
        
           |  |  | 771 | /**
 | 
        
           |  |  | 772 |  * Adds lightbox hidden element that covers the whole node.
 | 
        
           |  |  | 773 |  *
 | 
        
           |  |  | 774 |  * @param {YUI} Y
 | 
        
           |  |  | 775 |  * @param {Node} the node lightbox should be added to
 | 
        
           |  |  | 776 |  * @retun {Node} created lightbox node
 | 
        
           |  |  | 777 |  */
 | 
        
           |  |  | 778 | M.util.add_lightbox = function(Y, node) {
 | 
        
           |  |  | 779 |     var WAITICON = {'pix':"i/loading_small",'component':'moodle'};
 | 
        
           |  |  | 780 |   | 
        
           |  |  | 781 |     // Check if lightbox is already there
 | 
        
           |  |  | 782 |     if (node.one('.lightbox')) {
 | 
        
           |  |  | 783 |         return node.one('.lightbox');
 | 
        
           |  |  | 784 |     }
 | 
        
           |  |  | 785 |   | 
        
           |  |  | 786 |     node.setStyle('position', 'relative');
 | 
        
           | 1441 | ariadna | 787 |   | 
        
           | 1 | efrain | 788 |     var waiticon = Y.Node.create('<img />')
 | 
        
           | 1441 | ariadna | 789 |         .setAttribute('src', M.util.image_url(WAITICON.pix, WAITICON.component))
 | 
        
           |  |  | 790 |         .addClass('icon');
 | 
        
           | 1 | efrain | 791 |   | 
        
           |  |  | 792 |     var lightbox = Y.Node.create('<div></div>')
 | 
        
           |  |  | 793 |     .setStyles({
 | 
        
           |  |  | 794 |         'opacity' : '.75',
 | 
        
           |  |  | 795 |         'position' : 'absolute',
 | 
        
           |  |  | 796 |         'width' : '100%',
 | 
        
           |  |  | 797 |         'height' : '100%',
 | 
        
           |  |  | 798 |         'top' : 0,
 | 
        
           |  |  | 799 |         'left' : 0,
 | 
        
           | 1441 | ariadna | 800 |         'paddingTop': '50%',
 | 
        
           | 1 | efrain | 801 |         'backgroundColor' : 'white',
 | 
        
           |  |  | 802 |         'textAlign' : 'center'
 | 
        
           |  |  | 803 |     })
 | 
        
           |  |  | 804 |     .setAttribute('class', 'lightbox')
 | 
        
           |  |  | 805 |     .hide();
 | 
        
           |  |  | 806 |   | 
        
           |  |  | 807 |     lightbox.appendChild(waiticon);
 | 
        
           |  |  | 808 |     node.append(lightbox);
 | 
        
           |  |  | 809 |     return lightbox;
 | 
        
           |  |  | 810 | }
 | 
        
           |  |  | 811 |   | 
        
           |  |  | 812 | /**
 | 
        
           |  |  | 813 |  * Appends a hidden spinner element to the specified node.
 | 
        
           |  |  | 814 |  *
 | 
        
           |  |  | 815 |  * @param {YUI} Y
 | 
        
           |  |  | 816 |  * @param {Node} the node the spinner should be added to
 | 
        
           |  |  | 817 |  * @return {Node} created spinner node
 | 
        
           |  |  | 818 |  */
 | 
        
           |  |  | 819 | M.util.add_spinner = function(Y, node) {
 | 
        
           |  |  | 820 |     var WAITICON = {'pix':"i/loading_small",'component':'moodle'};
 | 
        
           |  |  | 821 |   | 
        
           |  |  | 822 |     // Check if spinner is already there
 | 
        
           |  |  | 823 |     if (node.one('.spinner')) {
 | 
        
           |  |  | 824 |         return node.one('.spinner');
 | 
        
           |  |  | 825 |     }
 | 
        
           |  |  | 826 |   | 
        
           |  |  | 827 |     var spinner = Y.Node.create('<img />')
 | 
        
           |  |  | 828 |         .setAttribute('src', M.util.image_url(WAITICON.pix, WAITICON.component))
 | 
        
           | 1441 | ariadna | 829 |         .addClass('spinner icon')
 | 
        
           | 1 | efrain | 830 |         .hide();
 | 
        
           |  |  | 831 |   | 
        
           |  |  | 832 |     node.append(spinner);
 | 
        
           |  |  | 833 |     return spinner;
 | 
        
           |  |  | 834 | }
 | 
        
           |  |  | 835 |   | 
        
           |  |  | 836 | /**
 | 
        
           |  |  | 837 |  * @deprecated since Moodle 3.3.
 | 
        
           |  |  | 838 |  */
 | 
        
           |  |  | 839 | function checkall() {
 | 
        
           |  |  | 840 |     throw new Error('checkall can not be used any more. Please use jQuery instead.');
 | 
        
           |  |  | 841 | }
 | 
        
           |  |  | 842 |   | 
        
           |  |  | 843 | /**
 | 
        
           |  |  | 844 |  * @deprecated since Moodle 3.3.
 | 
        
           |  |  | 845 |  */
 | 
        
           |  |  | 846 | function checknone() {
 | 
        
           |  |  | 847 |     throw new Error('checknone can not be used any more. Please use jQuery instead.');
 | 
        
           |  |  | 848 | }
 | 
        
           |  |  | 849 |   | 
        
           |  |  | 850 | /**
 | 
        
           |  |  | 851 |  * @deprecated since Moodle 3.3.
 | 
        
           |  |  | 852 |  */
 | 
        
           |  |  | 853 | function select_all_in_element_with_id(id, checked) {
 | 
        
           |  |  | 854 |     throw new Error('select_all_in_element_with_id can not be used any more. Please use jQuery instead.');
 | 
        
           |  |  | 855 | }
 | 
        
           |  |  | 856 |   | 
        
           |  |  | 857 | /**
 | 
        
           |  |  | 858 |  * @deprecated since Moodle 3.3.
 | 
        
           |  |  | 859 |  */
 | 
        
           |  |  | 860 | function select_all_in(elTagName, elClass, elId) {
 | 
        
           |  |  | 861 |     throw new Error('select_all_in can not be used any more. Please use jQuery instead.');
 | 
        
           |  |  | 862 | }
 | 
        
           |  |  | 863 |   | 
        
           |  |  | 864 | /**
 | 
        
           |  |  | 865 |  * @deprecated since Moodle 3.3.
 | 
        
           |  |  | 866 |  */
 | 
        
           |  |  | 867 | function deselect_all_in(elTagName, elClass, elId) {
 | 
        
           |  |  | 868 |     throw new Error('deselect_all_in can not be used any more. Please use jQuery instead.');
 | 
        
           |  |  | 869 | }
 | 
        
           |  |  | 870 |   | 
        
           |  |  | 871 | /**
 | 
        
           |  |  | 872 |  * @deprecated since Moodle 3.3.
 | 
        
           |  |  | 873 |  */
 | 
        
           |  |  | 874 | function confirm_if(expr, message) {
 | 
        
           |  |  | 875 |     throw new Error('confirm_if can not be used any more.');
 | 
        
           |  |  | 876 | }
 | 
        
           |  |  | 877 |   | 
        
           |  |  | 878 | /**
 | 
        
           |  |  | 879 |  * @deprecated since Moodle 3.3.
 | 
        
           |  |  | 880 |  */
 | 
        
           |  |  | 881 | function findParentNode(el, elName, elClass, elId) {
 | 
        
           |  |  | 882 |     throw new Error('findParentNode can not be used any more. Please use jQuery instead.');
 | 
        
           |  |  | 883 | }
 | 
        
           |  |  | 884 |   | 
        
           |  |  | 885 | function unmaskPassword(id) {
 | 
        
           |  |  | 886 |     var pw = document.getElementById(id);
 | 
        
           |  |  | 887 |     var chb = document.getElementById(id+'unmask');
 | 
        
           |  |  | 888 |   | 
        
           |  |  | 889 |     // MDL-30438 - The capability to changing the value of input type is not supported by IE8 or lower.
 | 
        
           |  |  | 890 |     // Replacing existing child with a new one, removed all yui properties for the node.  Therefore, this
 | 
        
           |  |  | 891 |     // functionality won't work in IE8 or lower.
 | 
        
           |  |  | 892 |     // This is a temporary fixed to allow other browsers to function properly.
 | 
        
           |  |  | 893 |     if (Y.UA.ie == 0 || Y.UA.ie >= 9) {
 | 
        
           |  |  | 894 |         if (chb.checked) {
 | 
        
           |  |  | 895 |             pw.type = "text";
 | 
        
           |  |  | 896 |         } else {
 | 
        
           |  |  | 897 |             pw.type = "password";
 | 
        
           |  |  | 898 |         }
 | 
        
           |  |  | 899 |     } else {  //IE Browser version 8 or lower
 | 
        
           |  |  | 900 |         try {
 | 
        
           |  |  | 901 |             // first try IE way - it can not set name attribute later
 | 
        
           |  |  | 902 |             if (chb.checked) {
 | 
        
           |  |  | 903 |               var newpw = document.createElement('<input type="text" autocomplete="off" name="'+pw.name+'">');
 | 
        
           |  |  | 904 |             } else {
 | 
        
           |  |  | 905 |               var newpw = document.createElement('<input type="password" autocomplete="off" name="'+pw.name+'">');
 | 
        
           |  |  | 906 |             }
 | 
        
           |  |  | 907 |             newpw.attributes['class'].nodeValue = pw.attributes['class'].nodeValue;
 | 
        
           |  |  | 908 |         } catch (e) {
 | 
        
           |  |  | 909 |             var newpw = document.createElement('input');
 | 
        
           |  |  | 910 |             newpw.setAttribute('autocomplete', 'off');
 | 
        
           |  |  | 911 |             newpw.setAttribute('name', pw.name);
 | 
        
           |  |  | 912 |             if (chb.checked) {
 | 
        
           |  |  | 913 |               newpw.setAttribute('type', 'text');
 | 
        
           |  |  | 914 |             } else {
 | 
        
           |  |  | 915 |               newpw.setAttribute('type', 'password');
 | 
        
           |  |  | 916 |             }
 | 
        
           |  |  | 917 |             newpw.setAttribute('class', pw.getAttribute('class'));
 | 
        
           |  |  | 918 |         }
 | 
        
           |  |  | 919 |         newpw.id = pw.id;
 | 
        
           |  |  | 920 |         newpw.size = pw.size;
 | 
        
           |  |  | 921 |         newpw.onblur = pw.onblur;
 | 
        
           |  |  | 922 |         newpw.onchange = pw.onchange;
 | 
        
           |  |  | 923 |         newpw.value = pw.value;
 | 
        
           |  |  | 924 |         pw.parentNode.replaceChild(newpw, pw);
 | 
        
           |  |  | 925 |     }
 | 
        
           |  |  | 926 | }
 | 
        
           |  |  | 927 |   | 
        
           |  |  | 928 | /**
 | 
        
           |  |  | 929 |  * @deprecated since Moodle 3.3.
 | 
        
           |  |  | 930 |  */
 | 
        
           |  |  | 931 | function filterByParent(elCollection, parentFinder) {
 | 
        
           |  |  | 932 |     throw new Error('filterByParent can not be used any more. Please use jQuery instead.');
 | 
        
           |  |  | 933 | }
 | 
        
           |  |  | 934 |   | 
        
           |  |  | 935 | /**
 | 
        
           |  |  | 936 |  * @deprecated since Moodle 3.3, but shouldn't be used in earlier versions either.
 | 
        
           |  |  | 937 |  */
 | 
        
           |  |  | 938 | function fix_column_widths() {
 | 
        
           |  |  | 939 |     Y.log('fix_column_widths() no longer does anything. Please remove it from your code.', 'warn', 'javascript-static.js');
 | 
        
           |  |  | 940 | }
 | 
        
           |  |  | 941 |   | 
        
           |  |  | 942 | /**
 | 
        
           |  |  | 943 |  * @deprecated since Moodle 3.3, but shouldn't be used in earlier versions either.
 | 
        
           |  |  | 944 |  */
 | 
        
           |  |  | 945 | function fix_column_width(colName) {
 | 
        
           |  |  | 946 |     Y.log('fix_column_width() no longer does anything. Please remove it from your code.', 'warn', 'javascript-static.js');
 | 
        
           |  |  | 947 | }
 | 
        
           |  |  | 948 |   | 
        
           |  |  | 949 |   | 
        
           |  |  | 950 | /*
 | 
        
           |  |  | 951 |    Insert myValue at current cursor position
 | 
        
           |  |  | 952 |  */
 | 
        
           |  |  | 953 | function insertAtCursor(myField, myValue) {
 | 
        
           |  |  | 954 |     // IE support
 | 
        
           |  |  | 955 |     if (document.selection) {
 | 
        
           |  |  | 956 |         myField.focus();
 | 
        
           |  |  | 957 |         sel = document.selection.createRange();
 | 
        
           |  |  | 958 |         sel.text = myValue;
 | 
        
           |  |  | 959 |     }
 | 
        
           |  |  | 960 |     // Mozilla/Netscape support
 | 
        
           |  |  | 961 |     else if (myField.selectionStart || myField.selectionStart == '0') {
 | 
        
           |  |  | 962 |         var startPos = myField.selectionStart;
 | 
        
           |  |  | 963 |         var endPos = myField.selectionEnd;
 | 
        
           |  |  | 964 |         myField.value = myField.value.substring(0, startPos)
 | 
        
           |  |  | 965 |             + myValue + myField.value.substring(endPos, myField.value.length);
 | 
        
           |  |  | 966 |     } else {
 | 
        
           |  |  | 967 |         myField.value += myValue;
 | 
        
           |  |  | 968 |     }
 | 
        
           |  |  | 969 | }
 | 
        
           |  |  | 970 |   | 
        
           |  |  | 971 | /**
 | 
        
           |  |  | 972 |  * Increment a file name.
 | 
        
           |  |  | 973 |  *
 | 
        
           |  |  | 974 |  * @param string file name.
 | 
        
           |  |  | 975 |  * @param boolean ignoreextension do not extract the extension prior to appending the
 | 
        
           |  |  | 976 |  *                                suffix. Useful when incrementing folder names.
 | 
        
           |  |  | 977 |  * @return string the incremented file name.
 | 
        
           |  |  | 978 |  */
 | 
        
           |  |  | 979 | function increment_filename(filename, ignoreextension) {
 | 
        
           |  |  | 980 |     var extension = '';
 | 
        
           |  |  | 981 |     var basename = filename;
 | 
        
           |  |  | 982 |   | 
        
           |  |  | 983 |     // Split the file name into the basename + extension.
 | 
        
           |  |  | 984 |     if (!ignoreextension) {
 | 
        
           |  |  | 985 |         var dotpos = filename.lastIndexOf('.');
 | 
        
           |  |  | 986 |         if (dotpos !== -1) {
 | 
        
           |  |  | 987 |             basename = filename.substr(0, dotpos);
 | 
        
           |  |  | 988 |             extension = filename.substr(dotpos, filename.length);
 | 
        
           |  |  | 989 |         }
 | 
        
           |  |  | 990 |     }
 | 
        
           |  |  | 991 |   | 
        
           |  |  | 992 |     // Look to see if the name already has (NN) at the end of it.
 | 
        
           |  |  | 993 |     var number = 0;
 | 
        
           |  |  | 994 |     var hasnumber = basename.match(/^(.*) \((\d+)\)$/);
 | 
        
           |  |  | 995 |     if (hasnumber !== null) {
 | 
        
           |  |  | 996 |         // Note the current number & remove it from the basename.
 | 
        
           |  |  | 997 |         number = parseInt(hasnumber[2], 10);
 | 
        
           |  |  | 998 |         basename = hasnumber[1];
 | 
        
           |  |  | 999 |     }
 | 
        
           |  |  | 1000 |   | 
        
           |  |  | 1001 |     number++;
 | 
        
           |  |  | 1002 |     var newname = basename + ' (' + number + ')' + extension;
 | 
        
           |  |  | 1003 |     return newname;
 | 
        
           |  |  | 1004 | }
 | 
        
           |  |  | 1005 |   | 
        
           |  |  | 1006 | /**
 | 
        
           |  |  | 1007 |  * Return whether we are in right to left mode or not.
 | 
        
           |  |  | 1008 |  *
 | 
        
           |  |  | 1009 |  * @return boolean
 | 
        
           |  |  | 1010 |  */
 | 
        
           |  |  | 1011 | function right_to_left() {
 | 
        
           |  |  | 1012 |     var body = Y.one('body');
 | 
        
           |  |  | 1013 |     var rtl = false;
 | 
        
           |  |  | 1014 |     if (body && body.hasClass('dir-rtl')) {
 | 
        
           |  |  | 1015 |         rtl = true;
 | 
        
           |  |  | 1016 |     }
 | 
        
           |  |  | 1017 |     return rtl;
 | 
        
           |  |  | 1018 | }
 | 
        
           |  |  | 1019 |   | 
        
           |  |  | 1020 | function openpopup(event, args) {
 | 
        
           |  |  | 1021 |   | 
        
           |  |  | 1022 |     if (event) {
 | 
        
           |  |  | 1023 |         if (event.preventDefault) {
 | 
        
           |  |  | 1024 |             event.preventDefault();
 | 
        
           |  |  | 1025 |         } else {
 | 
        
           |  |  | 1026 |             event.returnValue = false;
 | 
        
           |  |  | 1027 |         }
 | 
        
           |  |  | 1028 |     }
 | 
        
           |  |  | 1029 |   | 
        
           |  |  | 1030 |     // Make sure the name argument is set and valid.
 | 
        
           |  |  | 1031 |     var nameregex = /[^a-z0-9_]/i;
 | 
        
           |  |  | 1032 |     if (typeof args.name !== 'string') {
 | 
        
           |  |  | 1033 |         args.name = '_blank';
 | 
        
           |  |  | 1034 |     } else if (args.name.match(nameregex)) {
 | 
        
           |  |  | 1035 |         // Cleans window name because IE does not support funky ones.
 | 
        
           |  |  | 1036 |         if (M.cfg.developerdebug) {
 | 
        
           |  |  | 1037 |             alert('DEVELOPER NOTICE: Invalid \'name\' passed to openpopup(): ' + args.name);
 | 
        
           |  |  | 1038 |         }
 | 
        
           |  |  | 1039 |         args.name = args.name.replace(nameregex, '_');
 | 
        
           |  |  | 1040 |     }
 | 
        
           |  |  | 1041 |   | 
        
           |  |  | 1042 |     var fullurl = args.url;
 | 
        
           |  |  | 1043 |     if (!args.url.match(/https?:\/\//)) {
 | 
        
           |  |  | 1044 |         fullurl = M.cfg.wwwroot + args.url;
 | 
        
           |  |  | 1045 |     }
 | 
        
           |  |  | 1046 |     if (args.fullscreen) {
 | 
        
           |  |  | 1047 |         args.options = args.options.
 | 
        
           |  |  | 1048 |                 replace(/top=\d+/, 'top=0').
 | 
        
           |  |  | 1049 |                 replace(/left=\d+/, 'left=0').
 | 
        
           |  |  | 1050 |                 replace(/width=\d+/, 'width=' + screen.availWidth).
 | 
        
           |  |  | 1051 |                 replace(/height=\d+/, 'height=' + screen.availHeight);
 | 
        
           |  |  | 1052 |     }
 | 
        
           |  |  | 1053 |     var windowobj = window.open(fullurl,args.name,args.options);
 | 
        
           |  |  | 1054 |     if (!windowobj) {
 | 
        
           |  |  | 1055 |         return true;
 | 
        
           |  |  | 1056 |     }
 | 
        
           |  |  | 1057 |   | 
        
           |  |  | 1058 |     if (args.fullscreen) {
 | 
        
           |  |  | 1059 |         // In some browser / OS combinations (E.g. Chrome on Windows), the
 | 
        
           |  |  | 1060 |         // window initially opens slighly too big. The width and heigh options
 | 
        
           |  |  | 1061 |         // seem to control the area inside the browser window, so what with
 | 
        
           |  |  | 1062 |         // scroll-bars, etc. the actual window is bigger than the screen.
 | 
        
           |  |  | 1063 |         // Therefore, we need to fix things up after the window is open.
 | 
        
           |  |  | 1064 |         var hackcount = 100;
 | 
        
           |  |  | 1065 |         var get_size_exactly_right = function() {
 | 
        
           |  |  | 1066 |             windowobj.moveTo(0, 0);
 | 
        
           |  |  | 1067 |             windowobj.resizeTo(screen.availWidth, screen.availHeight);
 | 
        
           |  |  | 1068 |   | 
        
           |  |  | 1069 |             // Unfortunately, it seems that in Chrome on Ubuntu, if you call
 | 
        
           |  |  | 1070 |             // something like windowobj.resizeTo(1280, 1024) too soon (up to
 | 
        
           |  |  | 1071 |             // about 50ms) after the window is open, then it actually behaves
 | 
        
           |  |  | 1072 |             // as if you called windowobj.resizeTo(0, 0). Therefore, we need to
 | 
        
           |  |  | 1073 |             // check that the resize actually worked, and if not, repeatedly try
 | 
        
           |  |  | 1074 |             // again after a short delay until it works (but with a limit of
 | 
        
           |  |  | 1075 |             // hackcount repeats.
 | 
        
           |  |  | 1076 |             if (hackcount > 0 && (windowobj.innerHeight < 10 || windowobj.innerWidth < 10)) {
 | 
        
           |  |  | 1077 |                 hackcount -= 1;
 | 
        
           |  |  | 1078 |                 setTimeout(get_size_exactly_right, 10);
 | 
        
           |  |  | 1079 |             }
 | 
        
           |  |  | 1080 |         }
 | 
        
           |  |  | 1081 |         setTimeout(get_size_exactly_right, 0);
 | 
        
           |  |  | 1082 |     }
 | 
        
           |  |  | 1083 |     windowobj.focus();
 | 
        
           |  |  | 1084 |   | 
        
           |  |  | 1085 |     return false;
 | 
        
           |  |  | 1086 | }
 | 
        
           |  |  | 1087 |   | 
        
           |  |  | 1088 | /** Close the current browser window. */
 | 
        
           |  |  | 1089 | function close_window(e) {
 | 
        
           |  |  | 1090 |     if (e.preventDefault) {
 | 
        
           |  |  | 1091 |         e.preventDefault();
 | 
        
           |  |  | 1092 |     } else {
 | 
        
           |  |  | 1093 |         e.returnValue = false;
 | 
        
           |  |  | 1094 |     }
 | 
        
           |  |  | 1095 |     window.close();
 | 
        
           |  |  | 1096 | }
 | 
        
           |  |  | 1097 |   | 
        
           |  |  | 1098 | /**
 | 
        
           |  |  | 1099 |  * Tranfer keyboard focus to the HTML element with the given id, if it exists.
 | 
        
           |  |  | 1100 |  * @param controlid the control id.
 | 
        
           |  |  | 1101 |  */
 | 
        
           |  |  | 1102 | function focuscontrol(controlid) {
 | 
        
           |  |  | 1103 |     var control = document.getElementById(controlid);
 | 
        
           |  |  | 1104 |     if (control) {
 | 
        
           |  |  | 1105 |         control.focus();
 | 
        
           |  |  | 1106 |     }
 | 
        
           |  |  | 1107 | }
 | 
        
           |  |  | 1108 |   | 
        
           |  |  | 1109 | /**
 | 
        
           |  |  | 1110 |  * Transfers keyboard focus to an HTML element based on the old style style of focus
 | 
        
           |  |  | 1111 |  * This function should be removed as soon as it is no longer used
 | 
        
           |  |  | 1112 |  */
 | 
        
           |  |  | 1113 | function old_onload_focus(formid, controlname) {
 | 
        
           |  |  | 1114 |     if (document.forms[formid] && document.forms[formid].elements && document.forms[formid].elements[controlname]) {
 | 
        
           |  |  | 1115 |         document.forms[formid].elements[controlname].focus();
 | 
        
           |  |  | 1116 |     }
 | 
        
           |  |  | 1117 | }
 | 
        
           |  |  | 1118 |   | 
        
           |  |  | 1119 | function build_querystring(obj) {
 | 
        
           |  |  | 1120 |     return convert_object_to_string(obj, '&');
 | 
        
           |  |  | 1121 | }
 | 
        
           |  |  | 1122 |   | 
        
           |  |  | 1123 | function build_windowoptionsstring(obj) {
 | 
        
           |  |  | 1124 |     return convert_object_to_string(obj, ',');
 | 
        
           |  |  | 1125 | }
 | 
        
           |  |  | 1126 |   | 
        
           |  |  | 1127 | function convert_object_to_string(obj, separator) {
 | 
        
           |  |  | 1128 |     if (typeof obj !== 'object') {
 | 
        
           |  |  | 1129 |         return null;
 | 
        
           |  |  | 1130 |     }
 | 
        
           |  |  | 1131 |     var list = [];
 | 
        
           |  |  | 1132 |     for(var k in obj) {
 | 
        
           |  |  | 1133 |         k = encodeURIComponent(k);
 | 
        
           |  |  | 1134 |         var value = obj[k];
 | 
        
           |  |  | 1135 |         if(obj[k] instanceof Array) {
 | 
        
           |  |  | 1136 |             for(var i in value) {
 | 
        
           |  |  | 1137 |                 list.push(k+'[]='+encodeURIComponent(value[i]));
 | 
        
           |  |  | 1138 |             }
 | 
        
           |  |  | 1139 |         } else {
 | 
        
           |  |  | 1140 |             list.push(k+'='+encodeURIComponent(value));
 | 
        
           |  |  | 1141 |         }
 | 
        
           |  |  | 1142 |     }
 | 
        
           |  |  | 1143 |     return list.join(separator);
 | 
        
           |  |  | 1144 | }
 | 
        
           |  |  | 1145 |   | 
        
           |  |  | 1146 | /**
 | 
        
           |  |  | 1147 |  * @deprecated since Moodle 3.3.
 | 
        
           |  |  | 1148 |  */
 | 
        
           |  |  | 1149 | function stripHTML(str) {
 | 
        
           |  |  | 1150 |     throw new Error('stripHTML can not be used any more. Please use jQuery instead.');
 | 
        
           |  |  | 1151 | }
 | 
        
           |  |  | 1152 |   | 
        
           | 1441 | ariadna | 1153 | // eslint-disable-next-line no-unused-vars
 | 
        
           |  |  | 1154 | function updateProgressBar(id, percent, msg, estimate, error) {
 | 
        
           | 1 | efrain | 1155 |     var event,
 | 
        
           |  |  | 1156 |         el = document.getElementById(id),
 | 
        
           |  |  | 1157 |         eventData = {};
 | 
        
           |  |  | 1158 |   | 
        
           |  |  | 1159 |     if (!el) {
 | 
        
           |  |  | 1160 |         return;
 | 
        
           |  |  | 1161 |     }
 | 
        
           |  |  | 1162 |   | 
        
           |  |  | 1163 |     eventData.message = msg;
 | 
        
           |  |  | 1164 |     eventData.percent = percent;
 | 
        
           |  |  | 1165 |     eventData.estimate = estimate;
 | 
        
           | 1441 | ariadna | 1166 |     eventData.error = error;
 | 
        
           | 1 | efrain | 1167 |   | 
        
           |  |  | 1168 |     try {
 | 
        
           |  |  | 1169 |         event = new CustomEvent('update', {
 | 
        
           |  |  | 1170 |             bubbles: false,
 | 
        
           |  |  | 1171 |             cancelable: true,
 | 
        
           |  |  | 1172 |             detail: eventData
 | 
        
           |  |  | 1173 |         });
 | 
        
           |  |  | 1174 |     } catch (exception) {
 | 
        
           |  |  | 1175 |         if (!(exception instanceof TypeError)) {
 | 
        
           |  |  | 1176 |             throw exception;
 | 
        
           |  |  | 1177 |         }
 | 
        
           |  |  | 1178 |         event = document.createEvent('CustomEvent');
 | 
        
           |  |  | 1179 |         event.initCustomEvent('update', false, true, eventData);
 | 
        
           |  |  | 1180 |         event.prototype = window.Event.prototype;
 | 
        
           |  |  | 1181 |     }
 | 
        
           |  |  | 1182 |   | 
        
           |  |  | 1183 |     el.dispatchEvent(event);
 | 
        
           |  |  | 1184 | }
 | 
        
           |  |  | 1185 |   | 
        
           |  |  | 1186 | M.util.help_popups = {
 | 
        
           |  |  | 1187 |     setup : function(Y) {
 | 
        
           |  |  | 1188 |         Y.one('body').delegate('click', this.open_popup, 'a.helplinkpopup', this);
 | 
        
           |  |  | 1189 |     },
 | 
        
           |  |  | 1190 |     open_popup : function(e) {
 | 
        
           |  |  | 1191 |         // Prevent the default page action
 | 
        
           |  |  | 1192 |         e.preventDefault();
 | 
        
           |  |  | 1193 |   | 
        
           |  |  | 1194 |         // Grab the anchor that was clicked
 | 
        
           |  |  | 1195 |         var anchor = e.target.ancestor('a', true);
 | 
        
           |  |  | 1196 |         var args = {
 | 
        
           |  |  | 1197 |             'name'          : 'popup',
 | 
        
           |  |  | 1198 |             'url'           : anchor.getAttribute('href'),
 | 
        
           |  |  | 1199 |             'options'       : ''
 | 
        
           |  |  | 1200 |         };
 | 
        
           |  |  | 1201 |         var options = [
 | 
        
           |  |  | 1202 |             'height=600',
 | 
        
           |  |  | 1203 |             'width=800',
 | 
        
           |  |  | 1204 |             'top=0',
 | 
        
           |  |  | 1205 |             'left=0',
 | 
        
           |  |  | 1206 |             'menubar=0',
 | 
        
           |  |  | 1207 |             'location=0',
 | 
        
           |  |  | 1208 |             'scrollbars',
 | 
        
           |  |  | 1209 |             'resizable',
 | 
        
           |  |  | 1210 |             'toolbar',
 | 
        
           |  |  | 1211 |             'status',
 | 
        
           |  |  | 1212 |             'directories=0',
 | 
        
           |  |  | 1213 |             'fullscreen=0',
 | 
        
           |  |  | 1214 |             'dependent'
 | 
        
           |  |  | 1215 |         ]
 | 
        
           |  |  | 1216 |         args.options = options.join(',');
 | 
        
           |  |  | 1217 |   | 
        
           |  |  | 1218 |         openpopup(e, args);
 | 
        
           |  |  | 1219 |     }
 | 
        
           |  |  | 1220 | }
 | 
        
           |  |  | 1221 |   | 
        
           |  |  | 1222 | /**
 | 
        
           |  |  | 1223 |  * Custom menu namespace
 | 
        
           |  |  | 1224 |  */
 | 
        
           |  |  | 1225 | M.core_custom_menu = {
 | 
        
           |  |  | 1226 |     /**
 | 
        
           |  |  | 1227 |      * This method is used to initialise a custom menu given the id that belongs
 | 
        
           |  |  | 1228 |      * to the custom menu's root node.
 | 
        
           |  |  | 1229 |      *
 | 
        
           |  |  | 1230 |      * @param {YUI} Y
 | 
        
           |  |  | 1231 |      * @param {string} nodeid
 | 
        
           |  |  | 1232 |      */
 | 
        
           |  |  | 1233 |     init : function(Y, nodeid) {
 | 
        
           |  |  | 1234 |         var node = Y.one('#'+nodeid);
 | 
        
           |  |  | 1235 |         if (node) {
 | 
        
           |  |  | 1236 |             Y.use('node-menunav', function(Y) {
 | 
        
           |  |  | 1237 |                 // Get the node
 | 
        
           |  |  | 1238 |                 // Remove the javascript-disabled class.... obviously javascript is enabled.
 | 
        
           |  |  | 1239 |                 node.removeClass('javascript-disabled');
 | 
        
           |  |  | 1240 |                 // Initialise the menunav plugin
 | 
        
           |  |  | 1241 |                 node.plug(Y.Plugin.NodeMenuNav);
 | 
        
           |  |  | 1242 |             });
 | 
        
           |  |  | 1243 |         }
 | 
        
           |  |  | 1244 |     }
 | 
        
           |  |  | 1245 | };
 | 
        
           |  |  | 1246 |   | 
        
           |  |  | 1247 | /**
 | 
        
           |  |  | 1248 |  * Used to store form manipulation methods and enhancments
 | 
        
           |  |  | 1249 |  */
 | 
        
           |  |  | 1250 | M.form = M.form || {};
 | 
        
           |  |  | 1251 |   | 
        
           |  |  | 1252 | /**
 | 
        
           |  |  | 1253 |  * Converts a nbsp indented select box into a multi drop down custom control much
 | 
        
           |  |  | 1254 |  * like the custom menu. Can no longer be used.
 | 
        
           |  |  | 1255 |  * @deprecated since Moodle 3.3
 | 
        
           |  |  | 1256 |  */
 | 
        
           |  |  | 1257 | M.form.init_smartselect = function() {
 | 
        
           |  |  | 1258 |     throw new Error('M.form.init_smartselect can not be used any more.');
 | 
        
           |  |  | 1259 | };
 | 
        
           |  |  | 1260 |   | 
        
           |  |  | 1261 | /**
 | 
        
           |  |  | 1262 |  * Initiates the listeners for skiplink interaction
 | 
        
           |  |  | 1263 |  *
 | 
        
           |  |  | 1264 |  * @param {YUI} Y
 | 
        
           |  |  | 1265 |  */
 | 
        
           |  |  | 1266 | M.util.init_skiplink = function(Y) {
 | 
        
           |  |  | 1267 |     Y.one(Y.config.doc.body).delegate('click', function(e) {
 | 
        
           |  |  | 1268 |         e.preventDefault();
 | 
        
           |  |  | 1269 |         e.stopPropagation();
 | 
        
           |  |  | 1270 |         var node = Y.one(this.getAttribute('href'));
 | 
        
           |  |  | 1271 |         node.setAttribute('tabindex', '-1');
 | 
        
           |  |  | 1272 |         node.focus();
 | 
        
           |  |  | 1273 |         return true;
 | 
        
           |  |  | 1274 |     }, 'a.skip');
 | 
        
           |  |  | 1275 | };
 |