[ Index ] |
PHP Cross Reference of moodle-2.8 |
[Summary view] [Print] [Text view]
1 // Miscellaneous core Javascript functions for Moodle 2 // Global M object is initilised in inline javascript 3 4 /** 5 * Add module to list of available modules that can be loaded from YUI. 6 * @param {Array} modules 7 */ 8 M.yui.add_module = function(modules) { 9 for (var modname in modules) { 10 YUI_config.modules[modname] = modules[modname]; 11 } 12 }; 13 /** 14 * The gallery version to use when loading YUI modules from the gallery. 15 * Will be changed every time when using local galleries. 16 */ 17 M.yui.galleryversion = '2010.04.21-21-51'; 18 19 /** 20 * Various utility functions 21 */ 22 M.util = M.util || {}; 23 24 /** 25 * Language strings - initialised from page footer. 26 */ 27 M.str = M.str || {}; 28 29 /** 30 * Returns url for images. 31 * @param {String} imagename 32 * @param {String} component 33 * @return {String} 34 */ 35 M.util.image_url = function(imagename, component) { 36 37 if (!component || component == '' || component == 'moodle' || component == 'core') { 38 component = 'core'; 39 } 40 41 var url = M.cfg.wwwroot + '/theme/image.php'; 42 if (M.cfg.themerev > 0 && M.cfg.slasharguments == 1) { 43 if (!M.cfg.svgicons) { 44 url += '/_s'; 45 } 46 url += '/' + M.cfg.theme + '/' + component + '/' + M.cfg.themerev + '/' + imagename; 47 } else { 48 url += '?theme=' + M.cfg.theme + '&component=' + component + '&rev=' + M.cfg.themerev + '&image=' + imagename; 49 if (!M.cfg.svgicons) { 50 url += '&svg=0'; 51 } 52 } 53 54 return url; 55 }; 56 57 M.util.in_array = function(item, array){ 58 for( var i = 0; i<array.length; i++){ 59 if(item==array[i]){ 60 return true; 61 } 62 } 63 return false; 64 }; 65 66 /** 67 * Init a collapsible region, see print_collapsible_region in weblib.php 68 * @param {YUI} Y YUI3 instance with all libraries loaded 69 * @param {String} id the HTML id for the div. 70 * @param {String} userpref the user preference that records the state of this box. false if none. 71 * @param {String} strtooltip 72 */ 73 M.util.init_collapsible_region = function(Y, id, userpref, strtooltip) { 74 Y.use('anim', function(Y) { 75 new M.util.CollapsibleRegion(Y, id, userpref, strtooltip); 76 }); 77 }; 78 79 /** 80 * Object to handle a collapsible region : instantiate and forget styled object 81 * 82 * @class 83 * @constructor 84 * @param {YUI} Y YUI3 instance with all libraries loaded 85 * @param {String} id The HTML id for the div. 86 * @param {String} userpref The user preference that records the state of this box. false if none. 87 * @param {String} strtooltip 88 */ 89 M.util.CollapsibleRegion = function(Y, id, userpref, strtooltip) { 90 // Record the pref name 91 this.userpref = userpref; 92 93 // Find the divs in the document. 94 this.div = Y.one('#'+id); 95 96 // Get the caption for the collapsible region 97 var caption = this.div.one('#'+id + '_caption'); 98 99 // Create a link 100 var a = Y.Node.create('<a href="#"></a>'); 101 a.setAttribute('title', strtooltip); 102 103 // Get all the nodes from caption, remove them and append them to <a> 104 while (caption.hasChildNodes()) { 105 child = caption.get('firstChild'); 106 child.remove(); 107 a.append(child); 108 } 109 caption.append(a); 110 111 // Get the height of the div at this point before we shrink it if required 112 var height = this.div.get('offsetHeight'); 113 var collapsedimage = 't/collapsed'; // ltr mode 114 if (right_to_left()) { 115 collapsedimage = 't/collapsed_rtl'; 116 } else { 117 collapsedimage = 't/collapsed'; 118 } 119 if (this.div.hasClass('collapsed')) { 120 // Add the correct image and record the YUI node created in the process 121 this.icon = Y.Node.create('<img src="'+M.util.image_url(collapsedimage, 'moodle')+'" alt="" />'); 122 // Shrink the div as it is collapsed by default 123 this.div.setStyle('height', caption.get('offsetHeight')+'px'); 124 } else { 125 // Add the correct image and record the YUI node created in the process 126 this.icon = Y.Node.create('<img src="'+M.util.image_url('t/expanded', 'moodle')+'" alt="" />'); 127 } 128 a.append(this.icon); 129 130 // Create the animation. 131 var animation = new Y.Anim({ 132 node: this.div, 133 duration: 0.3, 134 easing: Y.Easing.easeBoth, 135 to: {height:caption.get('offsetHeight')}, 136 from: {height:height} 137 }); 138 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 } 153 }, this); 154 155 // Hook up the event handler. 156 a.on('click', function(e, animation) { 157 e.preventDefault(); 158 // Animate to the appropriate size. 159 if (animation.get('running')) { 160 animation.stop(); 161 } 162 animation.set('reverse', this.div.hasClass('collapsed')); 163 // Update the user preference. 164 if (this.userpref) { 165 M.util.set_user_preference(this.userpref, !this.div.hasClass('collapsed')); 166 } 167 animation.run(); 168 }, this, animation); 169 }; 170 171 /** 172 * The user preference that stores the state of this box. 173 * @property userpref 174 * @type String 175 */ 176 M.util.CollapsibleRegion.prototype.userpref = null; 177 178 /** 179 * The key divs that make up this 180 * @property div 181 * @type Y.Node 182 */ 183 M.util.CollapsibleRegion.prototype.div = null; 184 185 /** 186 * The key divs that make up this 187 * @property icon 188 * @type Y.Node 189 */ 190 M.util.CollapsibleRegion.prototype.icon = null; 191 192 /** 193 * Makes a best effort to connect back to Moodle to update a user preference, 194 * however, there is no mechanism for finding out if the update succeeded. 195 * 196 * Before you can use this function in your JavsScript, you must have called 197 * user_preference_allow_ajax_update from moodlelib.php to tell Moodle that 198 * the udpate is allowed, and how to safely clean and submitted values. 199 * 200 * @param String name the name of the setting to udpate. 201 * @param String the value to set it to. 202 */ 203 M.util.set_user_preference = function(name, value) { 204 YUI().use('io', function(Y) { 205 var url = M.cfg.wwwroot + '/lib/ajax/setuserpref.php?sesskey=' + 206 M.cfg.sesskey + '&pref=' + encodeURI(name) + '&value=' + encodeURI(value); 207 208 // If we are a developer, ensure that failures are reported. 209 var cfg = { 210 method: 'get', 211 on: {} 212 }; 213 if (M.cfg.developerdebug) { 214 cfg.on.failure = function(id, o, args) { 215 alert("Error updating user preference '" + name + "' using ajax. Clicking this link will repeat the Ajax call that failed so you can see the error: "); 216 } 217 } 218 219 // Make the request. 220 Y.io(url, cfg); 221 }); 222 }; 223 224 /** 225 * Prints a confirmation dialog in the style of DOM.confirm(). 226 * 227 * @method show_confirm_dialog 228 * @param {EventFacade} e 229 * @param {Object} args 230 * @param {String} args.message The question to ask the user 231 * @param {Function} [args.callback] A callback to apply on confirmation. 232 * @param {Object} [args.scope] The scope to use when calling the callback. 233 * @param {Object} [args.callbackargs] Any arguments to pass to the callback. 234 * @param {String} [args.cancellabel] The label to use on the cancel button. 235 * @param {String} [args.continuelabel] The label to use on the continue button. 236 */ 237 M.util.show_confirm_dialog = function(e, args) { 238 var target = e.target; 239 if (e.preventDefault) { 240 e.preventDefault(); 241 } 242 243 YUI().use('moodle-core-notification-confirm', function(Y) { 244 var confirmationDialogue = new M.core.confirm({ 245 width: '300px', 246 center: true, 247 modal: true, 248 visible: false, 249 draggable: false, 250 title: M.util.get_string('confirmation', 'admin'), 251 noLabel: M.util.get_string('cancel', 'moodle'), 252 question: args.message 253 }); 254 255 // The dialogue was submitted with a positive value indication. 256 confirmationDialogue.on('complete-yes', function(e) { 257 // Handle any callbacks. 258 if (args.callback) { 259 if (!Y.Lang.isFunction(args.callback)) { 260 Y.log('Callbacks to show_confirm_dialog must now be functions. Please update your code to pass in a function instead.', 261 'warn', 'M.util.show_confirm_dialog'); 262 return; 263 } 264 265 var scope = e.target; 266 if (Y.Lang.isObject(args.scope)) { 267 scope = args.scope; 268 } 269 270 var callbackargs = args.callbackargs || []; 271 args.callback.apply(scope, callbackargs); 272 return; 273 } 274 275 var targetancestor = null, 276 targetform = null; 277 278 if (target.test('a')) { 279 window.location = target.get('href'); 280 281 } else if ((targetancestor = target.ancestor('a')) !== null) { 282 window.location = targetancestor.get('href'); 283 284 } else if (target.test('input')) { 285 targetform = target.ancestor('form', true); 286 if (!targetform) { 287 return; 288 } 289 if (target.get('name') && target.get('value')) { 290 targetform.append('<input type="hidden" name="' + target.get('name') + 291 '" value="' + target.get('value') + '">'); 292 } 293 targetform.submit(); 294 295 } else if (target.test('form')) { 296 target.submit(); 297 298 } else { 299 Y.log("Element of type " + target.get('tagName') + 300 " is not supported by the M.util.show_confirm_dialog function. Use A, INPUT, or FORM", 301 'warn', 'javascript-static'); 302 } 303 }, this); 304 305 if (args.cancellabel) { 306 confirmationDialogue.set('noLabel', args.cancellabel); 307 } 308 309 if (args.continuelabel) { 310 confirmationDialogue.set('yesLabel', args.continuelabel); 311 } 312 313 confirmationDialogue.render() 314 .show(); 315 }); 316 }; 317 318 /** Useful for full embedding of various stuff */ 319 M.util.init_maximised_embed = function(Y, id) { 320 var obj = Y.one('#'+id); 321 if (!obj) { 322 return; 323 } 324 325 var get_htmlelement_size = function(el, prop) { 326 if (Y.Lang.isString(el)) { 327 el = Y.one('#' + el); 328 } 329 // Ensure element exists. 330 if (el) { 331 var val = el.getStyle(prop); 332 if (val == 'auto') { 333 val = el.getComputedStyle(prop); 334 } 335 val = parseInt(val); 336 if (isNaN(val)) { 337 return 0; 338 } 339 return val; 340 } else { 341 return 0; 342 } 343 }; 344 345 var resize_object = function() { 346 obj.setStyle('width', '0px'); 347 obj.setStyle('height', '0px'); 348 var newwidth = get_htmlelement_size('maincontent', 'width') - 35; 349 350 if (newwidth > 500) { 351 obj.setStyle('width', newwidth + 'px'); 352 } else { 353 obj.setStyle('width', '500px'); 354 } 355 356 var headerheight = get_htmlelement_size('page-header', 'height'); 357 var footerheight = get_htmlelement_size('page-footer', 'height'); 358 var newheight = parseInt(Y.one('body').get('winHeight')) - footerheight - headerheight - 100; 359 if (newheight < 400) { 360 newheight = 400; 361 } 362 obj.setStyle('height', newheight+'px'); 363 }; 364 365 resize_object(); 366 // fix layout if window resized too 367 window.onresize = function() { 368 resize_object(); 369 }; 370 }; 371 372 /** 373 * Breaks out all links to the top frame - used in frametop page layout. 374 */ 375 M.util.init_frametop = function(Y) { 376 Y.all('a').each(function(node) { 377 node.set('target', '_top'); 378 }); 379 Y.all('form').each(function(node) { 380 node.set('target', '_top'); 381 }); 382 }; 383 384 /** 385 * Finds all nodes that match the given CSS selector and attaches events to them 386 * so that they toggle a given classname when clicked. 387 * 388 * @param {YUI} Y 389 * @param {string} id An id containing elements to target 390 * @param {string} cssselector A selector to use to find targets 391 * @param {string} toggleclassname A classname to toggle 392 */ 393 M.util.init_toggle_class_on_click = function(Y, id, cssselector, toggleclassname, togglecssselector) { 394 395 if (togglecssselector == '') { 396 togglecssselector = cssselector; 397 } 398 399 var node = Y.one('#'+id); 400 node.all(cssselector).each(function(n){ 401 n.on('click', function(e){ 402 e.stopPropagation(); 403 if (e.target.test(cssselector) && !e.target.test('a') && !e.target.test('img')) { 404 if (this.test(togglecssselector)) { 405 this.toggleClass(toggleclassname); 406 } else { 407 this.ancestor(togglecssselector).toggleClass(toggleclassname); 408 } 409 } 410 }, n); 411 }); 412 // Attach this click event to the node rather than all selectors... will be much better 413 // for performance 414 node.on('click', function(e){ 415 if (e.target.hasClass('addtoall')) { 416 this.all(togglecssselector).addClass(toggleclassname); 417 } else if (e.target.hasClass('removefromall')) { 418 this.all(togglecssselector+'.'+toggleclassname).removeClass(toggleclassname); 419 } 420 }, node); 421 }; 422 423 /** 424 * Initialises a colour picker 425 * 426 * Designed to be used with admin_setting_configcolourpicker although could be used 427 * anywhere, just give a text input an id and insert a div with the class admin_colourpicker 428 * above or below the input (must have the same parent) and then call this with the 429 * id. 430 * 431 * This code was mostly taken from my [Sam Hemelryk] css theme tool available in 432 * contrib/blocks. For better docs refer to that. 433 * 434 * @param {YUI} Y 435 * @param {int} id 436 * @param {object} previewconf 437 */ 438 M.util.init_colour_picker = function(Y, id, previewconf) { 439 /** 440 * We need node and event-mouseenter 441 */ 442 Y.use('node', 'event-mouseenter', function(){ 443 /** 444 * The colour picker object 445 */ 446 var colourpicker = { 447 box : null, 448 input : null, 449 image : null, 450 preview : null, 451 current : null, 452 eventClick : null, 453 eventMouseEnter : null, 454 eventMouseLeave : null, 455 eventMouseMove : null, 456 width : 300, 457 height : 100, 458 factor : 5, 459 /** 460 * Initalises the colour picker by putting everything together and wiring the events 461 */ 462 init : function() { 463 this.input = Y.one('#'+id); 464 this.box = this.input.ancestor().one('.admin_colourpicker'); 465 this.image = Y.Node.create('<img alt="" class="colourdialogue" />'); 466 this.image.setAttribute('src', M.util.image_url('i/colourpicker', 'moodle')); 467 this.preview = Y.Node.create('<div class="previewcolour"></div>'); 468 this.preview.setStyle('width', this.height/2).setStyle('height', this.height/2).setStyle('backgroundColor', this.input.get('value')); 469 this.current = Y.Node.create('<div class="currentcolour"></div>'); 470 this.current.setStyle('width', this.height/2).setStyle('height', this.height/2 -1).setStyle('backgroundColor', this.input.get('value')); 471 this.box.setContent('').append(this.image).append(this.preview).append(this.current); 472 473 if (typeof(previewconf) === 'object' && previewconf !== null) { 474 Y.one('#'+id+'_preview').on('click', function(e){ 475 if (Y.Lang.isString(previewconf.selector)) { 476 Y.all(previewconf.selector).setStyle(previewconf.style, this.input.get('value')); 477 } else { 478 for (var i in previewconf.selector) { 479 Y.all(previewconf.selector[i]).setStyle(previewconf.style, this.input.get('value')); 480 } 481 } 482 }, this); 483 } 484 485 this.eventClick = this.image.on('click', this.pickColour, this); 486 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this); 487 }, 488 /** 489 * Starts to follow the mouse once it enter the image 490 */ 491 startFollow : function(e) { 492 this.eventMouseEnter.detach(); 493 this.eventMouseLeave = Y.on('mouseleave', this.endFollow, this.image, this); 494 this.eventMouseMove = this.image.on('mousemove', function(e){ 495 this.preview.setStyle('backgroundColor', this.determineColour(e)); 496 }, this); 497 }, 498 /** 499 * Stops following the mouse 500 */ 501 endFollow : function(e) { 502 this.eventMouseMove.detach(); 503 this.eventMouseLeave.detach(); 504 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this); 505 }, 506 /** 507 * Picks the colour the was clicked on 508 */ 509 pickColour : function(e) { 510 var colour = this.determineColour(e); 511 this.input.set('value', colour); 512 this.current.setStyle('backgroundColor', colour); 513 }, 514 /** 515 * Calculates the colour fromthe given co-ordinates 516 */ 517 determineColour : function(e) { 518 var eventx = Math.floor(e.pageX-e.target.getX()); 519 var eventy = Math.floor(e.pageY-e.target.getY()); 520 521 var imagewidth = this.width; 522 var imageheight = this.height; 523 var factor = this.factor; 524 var colour = [255,0,0]; 525 526 var matrices = [ 527 [ 0, 1, 0], 528 [ -1, 0, 0], 529 [ 0, 0, 1], 530 [ 0, -1, 0], 531 [ 1, 0, 0], 532 [ 0, 0, -1] 533 ]; 534 535 var matrixcount = matrices.length; 536 var limit = Math.round(imagewidth/matrixcount); 537 var heightbreak = Math.round(imageheight/2); 538 539 for (var x = 0; x < imagewidth; x++) { 540 var divisor = Math.floor(x / limit); 541 var matrix = matrices[divisor]; 542 543 colour[0] += matrix[0]*factor; 544 colour[1] += matrix[1]*factor; 545 colour[2] += matrix[2]*factor; 546 547 if (eventx==x) { 548 break; 549 } 550 } 551 552 var pixel = [colour[0], colour[1], colour[2]]; 553 if (eventy < heightbreak) { 554 pixel[0] += Math.floor(((255-pixel[0])/heightbreak) * (heightbreak - eventy)); 555 pixel[1] += Math.floor(((255-pixel[1])/heightbreak) * (heightbreak - eventy)); 556 pixel[2] += Math.floor(((255-pixel[2])/heightbreak) * (heightbreak - eventy)); 557 } else if (eventy > heightbreak) { 558 pixel[0] = Math.floor((imageheight-eventy)*(pixel[0]/heightbreak)); 559 pixel[1] = Math.floor((imageheight-eventy)*(pixel[1]/heightbreak)); 560 pixel[2] = Math.floor((imageheight-eventy)*(pixel[2]/heightbreak)); 561 } 562 563 return this.convert_rgb_to_hex(pixel); 564 }, 565 /** 566 * Converts an RGB value to Hex 567 */ 568 convert_rgb_to_hex : function(rgb) { 569 var hex = '#'; 570 var hexchars = "0123456789ABCDEF"; 571 for (var i=0; i<3; i++) { 572 var number = Math.abs(rgb[i]); 573 if (number == 0 || isNaN(number)) { 574 hex += '00'; 575 } else { 576 hex += hexchars.charAt((number-number%16)/16)+hexchars.charAt(number%16); 577 } 578 } 579 return hex; 580 } 581 }; 582 /** 583 * Initialise the colour picker :) Hoorah 584 */ 585 colourpicker.init(); 586 }); 587 }; 588 589 M.util.init_block_hider = function(Y, config) { 590 Y.use('base', 'node', function(Y) { 591 M.util.block_hider = M.util.block_hider || (function(){ 592 var blockhider = function() { 593 blockhider.superclass.constructor.apply(this, arguments); 594 }; 595 blockhider.prototype = { 596 initializer : function(config) { 597 this.set('block', '#'+this.get('id')); 598 var b = this.get('block'), 599 t = b.one('.title'), 600 a = null; 601 if (t && (a = t.one('.block_action'))) { 602 var hide = Y.Node.create('<img class="block-hider-hide" tabindex="0" alt="'+config.tooltipVisible+'" title="'+config.tooltipVisible+'" />'); 603 hide.setAttribute('src', this.get('iconVisible')).on('click', this.updateState, this, true); 604 hide.on('keypress', this.updateStateKey, this, true); 605 var show = Y.Node.create('<img class="block-hider-show" tabindex="0" alt="'+config.tooltipHidden+'" title="'+config.tooltipHidden+'" />'); 606 show.setAttribute('src', this.get('iconHidden')).on('click', this.updateState, this, false); 607 show.on('keypress', this.updateStateKey, this, false); 608 a.insert(show, 0).insert(hide, 0); 609 } 610 }, 611 updateState : function(e, hide) { 612 M.util.set_user_preference(this.get('preference'), hide); 613 if (hide) { 614 this.get('block').addClass('hidden'); 615 } else { 616 this.get('block').removeClass('hidden'); 617 } 618 }, 619 updateStateKey : function(e, hide) { 620 if (e.keyCode == 13) { //allow hide/show via enter key 621 this.updateState(this, hide); 622 } 623 } 624 }; 625 Y.extend(blockhider, Y.Base, blockhider.prototype, { 626 NAME : 'blockhider', 627 ATTRS : { 628 id : {}, 629 preference : {}, 630 iconVisible : { 631 value : M.util.image_url('t/switch_minus', 'moodle') 632 }, 633 iconHidden : { 634 value : M.util.image_url('t/switch_plus', 'moodle') 635 }, 636 block : { 637 setter : function(node) { 638 return Y.one(node); 639 } 640 } 641 } 642 }); 643 return blockhider; 644 })(); 645 new M.util.block_hider(config); 646 }); 647 }; 648 649 /** 650 * @var pending_js - The keys are the list of all pending js actions. 651 * @type Object 652 */ 653 M.util.pending_js = []; 654 M.util.complete_js = []; 655 656 /** 657 * Register any long running javascript code with a unique identifier. 658 * Should be followed with a call to js_complete with a matching 659 * idenfitier when the code is complete. May also be called with no arguments 660 * to test if there is any js calls pending. This is relied on by behat so that 661 * it can wait for all pending updates before interacting with a page. 662 * @param String uniqid - optional, if provided, 663 * registers this identifier until js_complete is called. 664 * @return boolean - True if there is any pending js. 665 */ 666 M.util.js_pending = function(uniqid) { 667 if (uniqid !== false) { 668 M.util.pending_js.push(uniqid); 669 } 670 671 return M.util.pending_js.length; 672 }; 673 674 // Start this asap. 675 M.util.js_pending('init'); 676 677 /** 678 * Register listeners for Y.io start/end so we can wait for them in behat. 679 */ 680 YUI.add('moodle-core-io', function(Y) { 681 Y.on('io:start', function(id) { 682 M.util.js_pending('io:' + id); 683 }); 684 Y.on('io:end', function(id) { 685 M.util.js_complete('io:' + id); 686 }); 687 }, '@VERSION@', { 688 condition: { 689 trigger: 'io-base', 690 when: 'after' 691 } 692 }); 693 694 /** 695 * Unregister any long running javascript code by unique identifier. 696 * This function should form a matching pair with js_pending 697 * 698 * @param String uniqid - required, unregisters this identifier 699 * @return boolean - True if there is any pending js. 700 */ 701 M.util.js_complete = function(uniqid) { 702 // Use the Y.Array.indexOf instead of the native because some older browsers do not support 703 // the native function. Y.Array polyfills the native function if it does not exist. 704 var index = Y.Array.indexOf(M.util.pending_js, uniqid); 705 if (index >= 0) { 706 M.util.complete_js.push(M.util.pending_js.splice(index, 1)); 707 } 708 709 return M.util.pending_js.length; 710 }; 711 712 /** 713 * Returns a string registered in advance for usage in JavaScript 714 * 715 * If you do not pass the third parameter, the function will just return 716 * the corresponding value from the M.str object. If the third parameter is 717 * provided, the function performs {$a} placeholder substitution in the 718 * same way as PHP get_string() in Moodle does. 719 * 720 * @param {String} identifier string identifier 721 * @param {String} component the component providing the string 722 * @param {Object|String} a optional variable to populate placeholder with 723 */ 724 M.util.get_string = function(identifier, component, a) { 725 var stringvalue; 726 727 if (M.cfg.developerdebug) { 728 // creating new instance if YUI is not optimal but it seems to be better way then 729 // require the instance via the function API - note that it is used in rare cases 730 // for debugging only anyway 731 // To ensure we don't kill browser performance if hundreds of get_string requests 732 // are made we cache the instance we generate within the M.util namespace. 733 // We don't publicly define the variable so that it doesn't get abused. 734 if (typeof M.util.get_string_yui_instance === 'undefined') { 735 M.util.get_string_yui_instance = new YUI({ debug : true }); 736 } 737 var Y = M.util.get_string_yui_instance; 738 } 739 740 if (!M.str.hasOwnProperty(component) || !M.str[component].hasOwnProperty(identifier)) { 741 stringvalue = '[[' + identifier + ',' + component + ']]'; 742 if (M.cfg.developerdebug) { 743 Y.log('undefined string ' + stringvalue, 'warn', 'M.util.get_string'); 744 } 745 return stringvalue; 746 } 747 748 stringvalue = M.str[component][identifier]; 749 750 if (typeof a == 'undefined') { 751 // no placeholder substitution requested 752 return stringvalue; 753 } 754 755 if (typeof a == 'number' || typeof a == 'string') { 756 // replace all occurrences of {$a} with the placeholder value 757 stringvalue = stringvalue.replace(/\{\$a\}/g, a); 758 return stringvalue; 759 } 760 761 if (typeof a == 'object') { 762 // replace {$a->key} placeholders 763 for (var key in a) { 764 if (typeof a[key] != 'number' && typeof a[key] != 'string') { 765 if (M.cfg.developerdebug) { 766 Y.log('invalid value type for $a->' + key, 'warn', 'M.util.get_string'); 767 } 768 continue; 769 } 770 var search = '{$a->' + key + '}'; 771 search = search.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); 772 search = new RegExp(search, 'g'); 773 stringvalue = stringvalue.replace(search, a[key]); 774 } 775 return stringvalue; 776 } 777 778 if (M.cfg.developerdebug) { 779 Y.log('incorrect placeholder type', 'warn', 'M.util.get_string'); 780 } 781 return stringvalue; 782 }; 783 784 /** 785 * Set focus on username or password field of the login form 786 */ 787 M.util.focus_login_form = function(Y) { 788 var username = Y.one('#username'); 789 var password = Y.one('#password'); 790 791 if (username == null || password == null) { 792 // something is wrong here 793 return; 794 } 795 796 var curElement = document.activeElement 797 if (curElement == 'undefined') { 798 // legacy browser - skip refocus protection 799 } else if (curElement.tagName == 'INPUT') { 800 // user was probably faster to focus something, do not mess with focus 801 return; 802 } 803 804 if (username.get('value') == '') { 805 username.focus(); 806 } else { 807 password.focus(); 808 } 809 } 810 811 /** 812 * Set focus on login error message 813 */ 814 M.util.focus_login_error = function(Y) { 815 var errorlog = Y.one('#loginerrormessage'); 816 817 if (errorlog) { 818 errorlog.focus(); 819 } 820 } 821 /** 822 * Adds lightbox hidden element that covers the whole node. 823 * 824 * @param {YUI} Y 825 * @param {Node} the node lightbox should be added to 826 * @retun {Node} created lightbox node 827 */ 828 M.util.add_lightbox = function(Y, node) { 829 var WAITICON = {'pix':"i/loading_small",'component':'moodle'}; 830 831 // Check if lightbox is already there 832 if (node.one('.lightbox')) { 833 return node.one('.lightbox'); 834 } 835 836 node.setStyle('position', 'relative'); 837 var waiticon = Y.Node.create('<img />') 838 .setAttrs({ 839 'src' : M.util.image_url(WAITICON.pix, WAITICON.component) 840 }) 841 .setStyles({ 842 'position' : 'relative', 843 'top' : '50%' 844 }); 845 846 var lightbox = Y.Node.create('<div></div>') 847 .setStyles({ 848 'opacity' : '.75', 849 'position' : 'absolute', 850 'width' : '100%', 851 'height' : '100%', 852 'top' : 0, 853 'left' : 0, 854 'backgroundColor' : 'white', 855 'textAlign' : 'center' 856 }) 857 .setAttribute('class', 'lightbox') 858 .hide(); 859 860 lightbox.appendChild(waiticon); 861 node.append(lightbox); 862 return lightbox; 863 } 864 865 /** 866 * Appends a hidden spinner element to the specified node. 867 * 868 * @param {YUI} Y 869 * @param {Node} the node the spinner should be added to 870 * @return {Node} created spinner node 871 */ 872 M.util.add_spinner = function(Y, node) { 873 var WAITICON = {'pix':"i/loading_small",'component':'moodle'}; 874 875 // Check if spinner is already there 876 if (node.one('.spinner')) { 877 return node.one('.spinner'); 878 } 879 880 var spinner = Y.Node.create('<img />') 881 .setAttribute('src', M.util.image_url(WAITICON.pix, WAITICON.component)) 882 .addClass('spinner') 883 .addClass('iconsmall') 884 .hide(); 885 886 node.append(spinner); 887 return spinner; 888 } 889 890 //=== old legacy JS code, hopefully to be replaced soon by M.xx.yy and YUI3 code === 891 892 function checkall() { 893 var inputs = document.getElementsByTagName('input'); 894 for (var i = 0; i < inputs.length; i++) { 895 if (inputs[i].type == 'checkbox') { 896 if (inputs[i].disabled || inputs[i].readOnly) { 897 continue; 898 } 899 inputs[i].checked = true; 900 } 901 } 902 } 903 904 function checknone() { 905 var inputs = document.getElementsByTagName('input'); 906 for (var i = 0; i < inputs.length; i++) { 907 if (inputs[i].type == 'checkbox') { 908 if (inputs[i].disabled || inputs[i].readOnly) { 909 continue; 910 } 911 inputs[i].checked = false; 912 } 913 } 914 } 915 916 /** 917 * Either check, or uncheck, all checkboxes inside the element with id is 918 * @param id the id of the container 919 * @param checked the new state, either '' or 'checked'. 920 */ 921 function select_all_in_element_with_id(id, checked) { 922 var container = document.getElementById(id); 923 if (!container) { 924 return; 925 } 926 var inputs = container.getElementsByTagName('input'); 927 for (var i = 0; i < inputs.length; ++i) { 928 if (inputs[i].type == 'checkbox' || inputs[i].type == 'radio') { 929 inputs[i].checked = checked; 930 } 931 } 932 } 933 934 function select_all_in(elTagName, elClass, elId) { 935 var inputs = document.getElementsByTagName('input'); 936 inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);}); 937 for(var i = 0; i < inputs.length; ++i) { 938 if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') { 939 inputs[i].checked = 'checked'; 940 } 941 } 942 } 943 944 function deselect_all_in(elTagName, elClass, elId) { 945 var inputs = document.getElementsByTagName('INPUT'); 946 inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);}); 947 for(var i = 0; i < inputs.length; ++i) { 948 if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') { 949 inputs[i].checked = ''; 950 } 951 } 952 } 953 954 function confirm_if(expr, message) { 955 if(!expr) { 956 return true; 957 } 958 return confirm(message); 959 } 960 961 962 /* 963 findParentNode (start, elementName, elementClass, elementID) 964 965 Travels up the DOM hierarchy to find a parent element with the 966 specified tag name, class, and id. All conditions must be met, 967 but any can be ommitted. Returns the BODY element if no match 968 found. 969 */ 970 function findParentNode(el, elName, elClass, elId) { 971 while (el.nodeName.toUpperCase() != 'BODY') { 972 if ((!elName || el.nodeName.toUpperCase() == elName) && 973 (!elClass || el.className.indexOf(elClass) != -1) && 974 (!elId || el.id == elId)) { 975 break; 976 } 977 el = el.parentNode; 978 } 979 return el; 980 } 981 /* 982 findChildNode (start, elementName, elementClass, elementID) 983 984 Travels down the DOM hierarchy to find all child elements with the 985 specified tag name, class, and id. All conditions must be met, 986 but any can be ommitted. 987 Doesn't examine children of matches. 988 989 @deprecated since Moodle 2.7 - please do not use this function any more. 990 @todo MDL-43242 This will be deleted in Moodle 2.9. 991 @see Y.all 992 */ 993 function findChildNodes(start, tagName, elementClass, elementID, elementName) { 994 Y.log("findChildNodes() is deprecated. Please use Y.all instead.", 995 "warn", "javascript-static.js"); 996 var children = new Array(); 997 for (var i = 0; i < start.childNodes.length; i++) { 998 var classfound = false; 999 var child = start.childNodes[i]; 1000 if((child.nodeType == 1) &&//element node type 1001 (elementClass && (typeof(child.className)=='string'))) { 1002 var childClasses = child.className.split(/\s+/); 1003 for (var childClassIndex in childClasses) { 1004 if (childClasses[childClassIndex]==elementClass) { 1005 classfound = true; 1006 break; 1007 } 1008 } 1009 } 1010 if(child.nodeType == 1) { //element node type 1011 if ( (!tagName || child.nodeName == tagName) && 1012 (!elementClass || classfound)&& 1013 (!elementID || child.id == elementID) && 1014 (!elementName || child.name == elementName)) 1015 { 1016 children = children.concat(child); 1017 } else { 1018 children = children.concat(findChildNodes(child, tagName, elementClass, elementID, elementName)); 1019 } 1020 } 1021 } 1022 return children; 1023 } 1024 1025 function unmaskPassword(id) { 1026 var pw = document.getElementById(id); 1027 var chb = document.getElementById(id+'unmask'); 1028 1029 // MDL-30438 - The capability to changing the value of input type is not supported by IE8 or lower. 1030 // Replacing existing child with a new one, removed all yui properties for the node. Therefore, this 1031 // functionality won't work in IE8 or lower. 1032 // This is a temporary fixed to allow other browsers to function properly. 1033 if (Y.UA.ie == 0 || Y.UA.ie >= 9) { 1034 if (chb.checked) { 1035 pw.type = "text"; 1036 } else { 1037 pw.type = "password"; 1038 } 1039 } else { //IE Browser version 8 or lower 1040 try { 1041 // first try IE way - it can not set name attribute later 1042 if (chb.checked) { 1043 var newpw = document.createElement('<input type="text" autocomplete="off" name="'+pw.name+'">'); 1044 } else { 1045 var newpw = document.createElement('<input type="password" autocomplete="off" name="'+pw.name+'">'); 1046 } 1047 newpw.attributes['class'].nodeValue = pw.attributes['class'].nodeValue; 1048 } catch (e) { 1049 var newpw = document.createElement('input'); 1050 newpw.setAttribute('autocomplete', 'off'); 1051 newpw.setAttribute('name', pw.name); 1052 if (chb.checked) { 1053 newpw.setAttribute('type', 'text'); 1054 } else { 1055 newpw.setAttribute('type', 'password'); 1056 } 1057 newpw.setAttribute('class', pw.getAttribute('class')); 1058 } 1059 newpw.id = pw.id; 1060 newpw.size = pw.size; 1061 newpw.onblur = pw.onblur; 1062 newpw.onchange = pw.onchange; 1063 newpw.value = pw.value; 1064 pw.parentNode.replaceChild(newpw, pw); 1065 } 1066 } 1067 1068 function filterByParent(elCollection, parentFinder) { 1069 var filteredCollection = []; 1070 for (var i = 0; i < elCollection.length; ++i) { 1071 var findParent = parentFinder(elCollection[i]); 1072 if (findParent.nodeName.toUpperCase() != 'BODY') { 1073 filteredCollection.push(elCollection[i]); 1074 } 1075 } 1076 return filteredCollection; 1077 } 1078 1079 /* 1080 All this is here just so that IE gets to handle oversized blocks 1081 in a visually pleasing manner. It does a browser detect. So sue me. 1082 */ 1083 1084 function fix_column_widths() { 1085 var agt = navigator.userAgent.toLowerCase(); 1086 if ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1)) { 1087 fix_column_width('left-column'); 1088 fix_column_width('right-column'); 1089 } 1090 } 1091 1092 function fix_column_width(colName) { 1093 if(column = document.getElementById(colName)) { 1094 if(!column.offsetWidth) { 1095 setTimeout("fix_column_width('" + colName + "')", 20); 1096 return; 1097 } 1098 1099 var width = 0; 1100 var nodes = column.childNodes; 1101 1102 for(i = 0; i < nodes.length; ++i) { 1103 if(nodes[i].className.indexOf("block") != -1 ) { 1104 if(width < nodes[i].offsetWidth) { 1105 width = nodes[i].offsetWidth; 1106 } 1107 } 1108 } 1109 1110 for(i = 0; i < nodes.length; ++i) { 1111 if(nodes[i].className.indexOf("block") != -1 ) { 1112 nodes[i].style.width = width + 'px'; 1113 } 1114 } 1115 } 1116 } 1117 1118 1119 /* 1120 Insert myValue at current cursor position 1121 */ 1122 function insertAtCursor(myField, myValue) { 1123 // IE support 1124 if (document.selection) { 1125 myField.focus(); 1126 sel = document.selection.createRange(); 1127 sel.text = myValue; 1128 } 1129 // Mozilla/Netscape support 1130 else if (myField.selectionStart || myField.selectionStart == '0') { 1131 var startPos = myField.selectionStart; 1132 var endPos = myField.selectionEnd; 1133 myField.value = myField.value.substring(0, startPos) 1134 + myValue + myField.value.substring(endPos, myField.value.length); 1135 } else { 1136 myField.value += myValue; 1137 } 1138 } 1139 1140 1141 /* 1142 Call instead of setting window.onload directly or setting body onload=. 1143 Adds your function to a chain of functions rather than overwriting anything 1144 that exists. 1145 @deprecated Since Moodle 2.7. This will be removed in Moodle 2.9. 1146 */ 1147 function addonload(fn) { 1148 Y.log('addonload has been deprecated since Moodle 2.7 and will be removed in Moodle 2.9', 1149 'warn', 'javascript-static.js'); 1150 var oldhandler=window.onload; 1151 window.onload=function() { 1152 if(oldhandler) oldhandler(); 1153 fn(); 1154 } 1155 } 1156 /** 1157 * Replacement for getElementsByClassName in browsers that aren't cool enough 1158 * 1159 * Relying on the built-in getElementsByClassName is far, far faster than 1160 * using YUI. 1161 * 1162 * Note: the third argument used to be an object with odd behaviour. It now 1163 * acts like the 'name' in the HTML5 spec, though the old behaviour is still 1164 * mimicked if you pass an object. 1165 * 1166 * @param {Node} oElm The top-level node for searching. To search a whole 1167 * document, use `document`. 1168 * @param {String} strTagName filter by tag names 1169 * @param {String} name same as HTML5 spec 1170 * @deprecated Since Moodle 2.7. This will be removed in Moodle 2.9. 1171 */ 1172 function getElementsByClassName(oElm, strTagName, name) { 1173 Y.log('getElementsByClassName has been deprecated since Moodle 2.7 and will be removed in Moodle 2.9', 1174 'warn', 'javascript-static.js'); 1175 // for backwards compatibility 1176 if(typeof name == "object") { 1177 var names = new Array(); 1178 for(var i=0; i<name.length; i++) names.push(names[i]); 1179 name = names.join(''); 1180 } 1181 // use native implementation if possible 1182 if (oElm.getElementsByClassName && Array.filter) { 1183 if (strTagName == '*') { 1184 return oElm.getElementsByClassName(name); 1185 } else { 1186 return Array.filter(oElm.getElementsByClassName(name), function(el) { 1187 return el.nodeName.toLowerCase() == strTagName.toLowerCase(); 1188 }); 1189 } 1190 } 1191 // native implementation unavailable, fall back to slow method 1192 var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName); 1193 var arrReturnElements = new Array(); 1194 var arrRegExpClassNames = new Array(); 1195 var names = name.split(' '); 1196 for(var i=0; i<names.length; i++) { 1197 arrRegExpClassNames.push(new RegExp("(^|\\s)" + names[i].replace(/\-/g, "\\-") + "(\\s|$)")); 1198 } 1199 var oElement; 1200 var bMatchesAll; 1201 for(var j=0; j<arrElements.length; j++) { 1202 oElement = arrElements[j]; 1203 bMatchesAll = true; 1204 for(var k=0; k<arrRegExpClassNames.length; k++) { 1205 if(!arrRegExpClassNames[k].test(oElement.className)) { 1206 bMatchesAll = false; 1207 break; 1208 } 1209 } 1210 if(bMatchesAll) { 1211 arrReturnElements.push(oElement); 1212 } 1213 } 1214 return (arrReturnElements) 1215 } 1216 1217 /** 1218 * Increment a file name. 1219 * 1220 * @param string file name. 1221 * @param boolean ignoreextension do not extract the extension prior to appending the 1222 * suffix. Useful when incrementing folder names. 1223 * @return string the incremented file name. 1224 */ 1225 function increment_filename(filename, ignoreextension) { 1226 var extension = ''; 1227 var basename = filename; 1228 1229 // Split the file name into the basename + extension. 1230 if (!ignoreextension) { 1231 var dotpos = filename.lastIndexOf('.'); 1232 if (dotpos !== -1) { 1233 basename = filename.substr(0, dotpos); 1234 extension = filename.substr(dotpos, filename.length); 1235 } 1236 } 1237 1238 // Look to see if the name already has (NN) at the end of it. 1239 var number = 0; 1240 var hasnumber = basename.match(/^(.*) \((\d+)\)$/); 1241 if (hasnumber !== null) { 1242 // Note the current number & remove it from the basename. 1243 number = parseInt(hasnumber[2], 10); 1244 basename = hasnumber[1]; 1245 } 1246 1247 number++; 1248 var newname = basename + ' (' + number + ')' + extension; 1249 return newname; 1250 } 1251 1252 /** 1253 * Return whether we are in right to left mode or not. 1254 * 1255 * @return boolean 1256 */ 1257 function right_to_left() { 1258 var body = Y.one('body'); 1259 var rtl = false; 1260 if (body && body.hasClass('dir-rtl')) { 1261 rtl = true; 1262 } 1263 return rtl; 1264 } 1265 1266 function openpopup(event, args) { 1267 1268 if (event) { 1269 if (event.preventDefault) { 1270 event.preventDefault(); 1271 } else { 1272 event.returnValue = false; 1273 } 1274 } 1275 1276 // Make sure the name argument is set and valid. 1277 var nameregex = /[^a-z0-9_]/i; 1278 if (typeof args.name !== 'string') { 1279 args.name = '_blank'; 1280 } else if (args.name.match(nameregex)) { 1281 // Cleans window name because IE does not support funky ones. 1282 if (M.cfg.developerdebug) { 1283 alert('DEVELOPER NOTICE: Invalid \'name\' passed to openpopup(): ' + args.name); 1284 } 1285 args.name = args.name.replace(nameregex, '_'); 1286 } 1287 1288 var fullurl = args.url; 1289 if (!args.url.match(/https?:\/\//)) { 1290 fullurl = M.cfg.wwwroot + args.url; 1291 } 1292 if (args.fullscreen) { 1293 args.options = args.options. 1294 replace(/top=\d+/, 'top=0'). 1295 replace(/left=\d+/, 'left=0'). 1296 replace(/width=\d+/, 'width=' + screen.availWidth). 1297 replace(/height=\d+/, 'height=' + screen.availHeight); 1298 } 1299 var windowobj = window.open(fullurl,args.name,args.options); 1300 if (!windowobj) { 1301 return true; 1302 } 1303 1304 if (args.fullscreen) { 1305 // In some browser / OS combinations (E.g. Chrome on Windows), the 1306 // window initially opens slighly too big. The width and heigh options 1307 // seem to control the area inside the browser window, so what with 1308 // scroll-bars, etc. the actual window is bigger than the screen. 1309 // Therefore, we need to fix things up after the window is open. 1310 var hackcount = 100; 1311 var get_size_exactly_right = function() { 1312 windowobj.moveTo(0, 0); 1313 windowobj.resizeTo(screen.availWidth, screen.availHeight); 1314 1315 // Unfortunately, it seems that in Chrome on Ubuntu, if you call 1316 // something like windowobj.resizeTo(1280, 1024) too soon (up to 1317 // about 50ms) after the window is open, then it actually behaves 1318 // as if you called windowobj.resizeTo(0, 0). Therefore, we need to 1319 // check that the resize actually worked, and if not, repeatedly try 1320 // again after a short delay until it works (but with a limit of 1321 // hackcount repeats. 1322 if (hackcount > 0 && (windowobj.innerHeight < 10 || windowobj.innerWidth < 10)) { 1323 hackcount -= 1; 1324 setTimeout(get_size_exactly_right, 10); 1325 } 1326 } 1327 setTimeout(get_size_exactly_right, 0); 1328 } 1329 windowobj.focus(); 1330 1331 return false; 1332 } 1333 1334 /** Close the current browser window. */ 1335 function close_window(e) { 1336 if (e.preventDefault) { 1337 e.preventDefault(); 1338 } else { 1339 e.returnValue = false; 1340 } 1341 window.close(); 1342 } 1343 1344 /** 1345 * Used in a couple of modules to hide navigation areas when using AJAX 1346 * @deprecated since Moodle 2.7. This function will be removed in Moodle 2.9. 1347 */ 1348 function show_item(itemid) { 1349 Y.log('show_item has been deprecated since Moodle 2.7 and will be removed in Moodle 2.9', 1350 'warn', 'javascript-static.js'); 1351 var item = Y.one('#' + itemid); 1352 if (item) { 1353 item.show(); 1354 } 1355 } 1356 1357 // Deprecated since Moodle 2.7. This function will be removed in Moodle 2.9. 1358 function destroy_item(itemid) { 1359 Y.log('destroy_item has been deprecated since Moodle 2.7 and will be removed in Moodle 2.9', 1360 'warn', 'javascript-static.js'); 1361 var item = Y.one('#' + itemid); 1362 if (item) { 1363 item.remove(true); 1364 } 1365 } 1366 /** 1367 * Tranfer keyboard focus to the HTML element with the given id, if it exists. 1368 * @param controlid the control id. 1369 */ 1370 function focuscontrol(controlid) { 1371 var control = document.getElementById(controlid); 1372 if (control) { 1373 control.focus(); 1374 } 1375 } 1376 1377 /** 1378 * Transfers keyboard focus to an HTML element based on the old style style of focus 1379 * This function should be removed as soon as it is no longer used 1380 */ 1381 function old_onload_focus(formid, controlname) { 1382 if (document.forms[formid] && document.forms[formid].elements && document.forms[formid].elements[controlname]) { 1383 document.forms[formid].elements[controlname].focus(); 1384 } 1385 } 1386 1387 function build_querystring(obj) { 1388 return convert_object_to_string(obj, '&'); 1389 } 1390 1391 function build_windowoptionsstring(obj) { 1392 return convert_object_to_string(obj, ','); 1393 } 1394 1395 function convert_object_to_string(obj, separator) { 1396 if (typeof obj !== 'object') { 1397 return null; 1398 } 1399 var list = []; 1400 for(var k in obj) { 1401 k = encodeURIComponent(k); 1402 var value = obj[k]; 1403 if(obj[k] instanceof Array) { 1404 for(var i in value) { 1405 list.push(k+'[]='+encodeURIComponent(value[i])); 1406 } 1407 } else { 1408 list.push(k+'='+encodeURIComponent(value)); 1409 } 1410 } 1411 return list.join(separator); 1412 } 1413 1414 function stripHTML(str) { 1415 var re = /<\S[^><]*>/g; 1416 var ret = str.replace(re, ""); 1417 return ret; 1418 } 1419 1420 function updateProgressBar(id, percent, msg, estimate) { 1421 var progressIndicator = Y.one('#' + id); 1422 if (!progressIndicator) { 1423 return; 1424 } 1425 1426 var progressBar = progressIndicator.one('.bar'), 1427 statusIndicator = progressIndicator.one('h2'), 1428 estimateIndicator = progressIndicator.one('p'); 1429 1430 statusIndicator.set('innerHTML', Y.Escape.html(msg)); 1431 progressBar.set('innerHTML', Y.Escape.html('' + percent + '%')); 1432 if (percent === 100) { 1433 progressIndicator.addClass('progress-success'); 1434 estimateIndicator.set('innerHTML', null); 1435 } else { 1436 if (estimate) { 1437 estimateIndicator.set('innerHTML', Y.Escape.html(estimate)); 1438 } else { 1439 estimateIndicator.set('innerHTML', null); 1440 } 1441 progressIndicator.removeClass('progress-success'); 1442 } 1443 progressBar.setAttribute('aria-valuenow', percent); 1444 progressBar.setStyle('width', percent + '%'); 1445 } 1446 1447 // ===== Deprecated core Javascript functions for Moodle ==== 1448 // DO NOT USE!!!!!!! 1449 // Do not put this stuff in separate file because it only adds extra load on servers! 1450 1451 /** 1452 * Used in a couple of modules to hide navigation areas when using AJAX 1453 * @deprecated since Moodle 2.7. This function will be removed in Moodle 2.9. 1454 */ 1455 function hide_item(itemid) { 1456 Y.log('hide_item has been deprecated since Moodle 2.7 and will be removed in Moodle 2.9', 1457 'warn', 'javascript-static.js'); 1458 var item = Y.one('#' + itemid); 1459 if (item) { 1460 item.hide(); 1461 } 1462 } 1463 1464 M.util.help_popups = { 1465 setup : function(Y) { 1466 Y.one('body').delegate('click', this.open_popup, 'a.helplinkpopup', this); 1467 }, 1468 open_popup : function(e) { 1469 // Prevent the default page action 1470 e.preventDefault(); 1471 1472 // Grab the anchor that was clicked 1473 var anchor = e.target.ancestor('a', true); 1474 var args = { 1475 'name' : 'popup', 1476 'url' : anchor.getAttribute('href'), 1477 'options' : '' 1478 }; 1479 var options = [ 1480 'height=600', 1481 'width=800', 1482 'top=0', 1483 'left=0', 1484 'menubar=0', 1485 'location=0', 1486 'scrollbars', 1487 'resizable', 1488 'toolbar', 1489 'status', 1490 'directories=0', 1491 'fullscreen=0', 1492 'dependent' 1493 ] 1494 args.options = options.join(','); 1495 1496 openpopup(e, args); 1497 } 1498 } 1499 1500 /** 1501 * Custom menu namespace 1502 */ 1503 M.core_custom_menu = { 1504 /** 1505 * This method is used to initialise a custom menu given the id that belongs 1506 * to the custom menu's root node. 1507 * 1508 * @param {YUI} Y 1509 * @param {string} nodeid 1510 */ 1511 init : function(Y, nodeid) { 1512 var node = Y.one('#'+nodeid); 1513 if (node) { 1514 Y.use('node-menunav', function(Y) { 1515 // Get the node 1516 // Remove the javascript-disabled class.... obviously javascript is enabled. 1517 node.removeClass('javascript-disabled'); 1518 // Initialise the menunav plugin 1519 node.plug(Y.Plugin.NodeMenuNav); 1520 }); 1521 } 1522 } 1523 }; 1524 1525 /** 1526 * Used to store form manipulation methods and enhancments 1527 */ 1528 M.form = M.form || {}; 1529 1530 /** 1531 * Converts a nbsp indented select box into a multi drop down custom control much 1532 * like the custom menu. It also selectable categories on or off. 1533 * 1534 * $form->init_javascript_enhancement('elementname','smartselect', array('selectablecategories'=>true|false, 'mode'=>'compact'|'spanning')); 1535 * 1536 * @param {YUI} Y 1537 * @param {string} id 1538 * @param {Array} options 1539 */ 1540 M.form.init_smartselect = function(Y, id, options) { 1541 if (!id.match(/^id_/)) { 1542 id = 'id_'+id; 1543 } 1544 var select = Y.one('select#'+id); 1545 if (!select) { 1546 return false; 1547 } 1548 Y.use('event-delegate',function(){ 1549 var smartselect = { 1550 id : id, 1551 structure : [], 1552 options : [], 1553 submenucount : 0, 1554 currentvalue : null, 1555 currenttext : null, 1556 shownevent : null, 1557 cfg : { 1558 selectablecategories : true, 1559 mode : null 1560 }, 1561 nodes : { 1562 select : null, 1563 loading : null, 1564 menu : null 1565 }, 1566 init : function(Y, id, args, nodes) { 1567 if (typeof(args)=='object') { 1568 for (var i in this.cfg) { 1569 if (args[i] || args[i]===false) { 1570 this.cfg[i] = args[i]; 1571 } 1572 } 1573 } 1574 1575 // Display a loading message first up 1576 this.nodes.select = nodes.select; 1577 1578 this.currentvalue = this.nodes.select.get('selectedIndex'); 1579 this.currenttext = this.nodes.select.all('option').item(this.currentvalue).get('innerHTML'); 1580 1581 var options = Array(); 1582 options[''] = {text:this.currenttext,value:'',depth:0,children:[]}; 1583 this.nodes.select.all('option').each(function(option, index) { 1584 var rawtext = option.get('innerHTML'); 1585 var text = rawtext.replace(/^( )*/, ''); 1586 if (rawtext === text) { 1587 text = rawtext.replace(/^(\s)*/, ''); 1588 var depth = (rawtext.length - text.length ) + 1; 1589 } else { 1590 var depth = ((rawtext.length - text.length )/12)+1; 1591 } 1592 option.set('innerHTML', text); 1593 options['i'+index] = {text:text,depth:depth,index:index,children:[]}; 1594 }, this); 1595 1596 this.structure = []; 1597 var structcount = 0; 1598 for (var i in options) { 1599 var o = options[i]; 1600 if (o.depth == 0) { 1601 this.structure.push(o); 1602 structcount++; 1603 } else { 1604 var d = o.depth; 1605 var current = this.structure[structcount-1]; 1606 for (var j = 0; j < o.depth-1;j++) { 1607 if (current && current.children) { 1608 current = current.children[current.children.length-1]; 1609 } 1610 } 1611 if (current && current.children) { 1612 current.children.push(o); 1613 } 1614 } 1615 } 1616 1617 this.nodes.menu = Y.Node.create(this.generate_menu_content()); 1618 this.nodes.menu.one('.smartselect_mask').setStyle('opacity', 0.01); 1619 this.nodes.menu.one('.smartselect_mask').setStyle('width', (this.nodes.select.get('offsetWidth')+5)+'px'); 1620 this.nodes.menu.one('.smartselect_mask').setStyle('height', (this.nodes.select.get('offsetHeight'))+'px'); 1621 1622 if (this.cfg.mode == null) { 1623 var formwidth = this.nodes.select.ancestor('form').get('offsetWidth'); 1624 if (formwidth < 400 || this.nodes.menu.get('offsetWidth') < formwidth*2) { 1625 this.cfg.mode = 'compact'; 1626 } else { 1627 this.cfg.mode = 'spanning'; 1628 } 1629 } 1630 1631 if (this.cfg.mode == 'compact') { 1632 this.nodes.menu.addClass('compactmenu'); 1633 } else { 1634 this.nodes.menu.addClass('spanningmenu'); 1635 this.nodes.menu.delegate('mouseover', this.show_sub_menu, '.smartselect_submenuitem', this); 1636 } 1637 1638 Y.one(document.body).append(this.nodes.menu); 1639 var pos = this.nodes.select.getXY(); 1640 pos[0] += 1; 1641 this.nodes.menu.setXY(pos); 1642 this.nodes.menu.on('click', this.handle_click, this); 1643 1644 Y.one(window).on('resize', function(){ 1645 var pos = this.nodes.select.getXY(); 1646 pos[0] += 1; 1647 this.nodes.menu.setXY(pos); 1648 }, this); 1649 }, 1650 generate_menu_content : function() { 1651 var content = '<div id="'+this.id+'_smart_select" class="smartselect">'; 1652 content += this.generate_submenu_content(this.structure[0], true); 1653 content += '</ul></div>'; 1654 return content; 1655 }, 1656 generate_submenu_content : function(item, rootelement) { 1657 this.submenucount++; 1658 var content = ''; 1659 if (item.children.length > 0) { 1660 if (rootelement) { 1661 content += '<div class="smartselect_mask" href="#ss_submenu'+this.submenucount+'"> </div>'; 1662 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_menu">'; 1663 content += '<div class="smartselect_menu_content">'; 1664 } else { 1665 content += '<li class="smartselect_submenuitem">'; 1666 var categoryclass = (this.cfg.selectablecategories)?'selectable':'notselectable'; 1667 content += '<a class="smartselect_menuitem_label '+categoryclass+'" href="#ss_submenu'+this.submenucount+'" value="'+item.index+'">'+item.text+'</a>'; 1668 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_submenu">'; 1669 content += '<div class="smartselect_submenu_content">'; 1670 } 1671 content += '<ul>'; 1672 for (var i in item.children) { 1673 content += this.generate_submenu_content(item.children[i],false); 1674 } 1675 content += '</ul>'; 1676 content += '</div>'; 1677 content += '</div>'; 1678 if (rootelement) { 1679 } else { 1680 content += '</li>'; 1681 } 1682 } else { 1683 content += '<li class="smartselect_menuitem">'; 1684 content += '<a class="smartselect_menuitem_content selectable" href="#" value="'+item.index+'">'+item.text+'</a>'; 1685 content += '</li>'; 1686 } 1687 return content; 1688 }, 1689 select : function(e) { 1690 var t = e.target; 1691 e.halt(); 1692 this.currenttext = t.get('innerHTML'); 1693 this.currentvalue = t.getAttribute('value'); 1694 this.nodes.select.set('selectedIndex', this.currentvalue); 1695 this.hide_menu(); 1696 }, 1697 handle_click : function(e) { 1698 var target = e.target; 1699 if (target.hasClass('smartselect_mask')) { 1700 this.show_menu(e); 1701 } else if (target.hasClass('selectable') || target.hasClass('smartselect_menuitem')) { 1702 this.select(e); 1703 } else if (target.hasClass('smartselect_menuitem_label') || target.hasClass('smartselect_submenuitem')) { 1704 this.show_sub_menu(e); 1705 } 1706 }, 1707 show_menu : function(e) { 1708 e.halt(); 1709 var menu = e.target.ancestor().one('.smartselect_menu'); 1710 menu.addClass('visible'); 1711 this.shownevent = Y.one(document.body).on('click', this.hide_menu, this); 1712 }, 1713 show_sub_menu : function(e) { 1714 e.halt(); 1715 var target = e.target; 1716 if (!target.hasClass('smartselect_submenuitem')) { 1717 target = target.ancestor('.smartselect_submenuitem'); 1718 } 1719 if (this.cfg.mode == 'compact' && target.one('.smartselect_submenu').hasClass('visible')) { 1720 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible'); 1721 return; 1722 } 1723 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible'); 1724 target.one('.smartselect_submenu').addClass('visible'); 1725 }, 1726 hide_menu : function() { 1727 this.nodes.menu.all('.visible').removeClass('visible'); 1728 if (this.shownevent) { 1729 this.shownevent.detach(); 1730 } 1731 } 1732 }; 1733 smartselect.init(Y, id, options, {select:select}); 1734 }); 1735 }; 1736 1737 /** List of flv players to be loaded */ 1738 M.util.video_players = []; 1739 /** List of mp3 players to be loaded */ 1740 M.util.audio_players = []; 1741 1742 /** 1743 * Add video player 1744 * @param id element id 1745 * @param fileurl media url 1746 * @param width 1747 * @param height 1748 * @param autosize true means detect size from media 1749 */ 1750 M.util.add_video_player = function (id, fileurl, width, height, autosize) { 1751 M.util.video_players.push({id: id, fileurl: fileurl, width: width, height: height, autosize: autosize, resized: false}); 1752 }; 1753 1754 /** 1755 * Add audio player. 1756 * @param id 1757 * @param fileurl 1758 * @param small 1759 */ 1760 M.util.add_audio_player = function (id, fileurl, small) { 1761 M.util.audio_players.push({id: id, fileurl: fileurl, small: small}); 1762 }; 1763 1764 /** 1765 * Initialise all audio and video player, must be called from page footer. 1766 */ 1767 M.util.load_flowplayer = function() { 1768 if (M.util.video_players.length == 0 && M.util.audio_players.length == 0) { 1769 return; 1770 } 1771 if (typeof(flowplayer) == 'undefined') { 1772 var loaded = false; 1773 1774 var embed_function = function() { 1775 if (loaded || typeof(flowplayer) == 'undefined') { 1776 return; 1777 } 1778 loaded = true; 1779 1780 var controls = { 1781 autoHide: true 1782 } 1783 /* TODO: add CSS color overrides for the flv flow player */ 1784 1785 for(var i=0; i<M.util.video_players.length; i++) { 1786 var video = M.util.video_players[i]; 1787 if (video.width > 0 && video.height > 0) { 1788 var src = {src: M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.18.swf', width: video.width, height: video.height}; 1789 } else { 1790 var src = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.18.swf'; 1791 } 1792 flowplayer(video.id, src, { 1793 plugins: {controls: controls}, 1794 clip: { 1795 url: video.fileurl, autoPlay: false, autoBuffering: true, scaling: 'fit', mvideo: video, 1796 onMetaData: function(clip) { 1797 if (clip.mvideo.autosize && !clip.mvideo.resized) { 1798 clip.mvideo.resized = true; 1799 //alert("metadata!!! "+clip.width+' '+clip.height+' '+JSON.stringify(clip.metaData)); 1800 if (typeof(clip.metaData.width) == 'undefined' || typeof(clip.metaData.height) == 'undefined') { 1801 // bad luck, we have to guess - we may not get metadata at all 1802 var width = clip.width; 1803 var height = clip.height; 1804 } else { 1805 var width = clip.metaData.width; 1806 var height = clip.metaData.height; 1807 } 1808 var minwidth = 300; // controls are messed up in smaller objects 1809 if (width < minwidth) { 1810 height = (height * minwidth) / width; 1811 width = minwidth; 1812 } 1813 1814 var object = this._api(); 1815 object.width = width; 1816 object.height = height; 1817 } 1818 } 1819 } 1820 }); 1821 } 1822 if (M.util.audio_players.length == 0) { 1823 return; 1824 } 1825 var controls = { 1826 autoHide: false, 1827 fullscreen: false, 1828 next: false, 1829 previous: false, 1830 scrubber: true, 1831 play: true, 1832 pause: true, 1833 volume: true, 1834 mute: false, 1835 backgroundGradient: [0.5,0,0.3] 1836 }; 1837 1838 var rule; 1839 for (var j=0; j < document.styleSheets.length; j++) { 1840 1841 // To avoid javascript security violation accessing cross domain stylesheets 1842 var allrules = false; 1843 try { 1844 if (typeof (document.styleSheets[j].rules) != 'undefined') { 1845 allrules = document.styleSheets[j].rules; 1846 } else if (typeof (document.styleSheets[j].cssRules) != 'undefined') { 1847 allrules = document.styleSheets[j].cssRules; 1848 } else { 1849 // why?? 1850 continue; 1851 } 1852 } catch (e) { 1853 continue; 1854 } 1855 1856 // On cross domain style sheets Chrome V8 allows access to rules but returns null 1857 if (!allrules) { 1858 continue; 1859 } 1860 1861 for(var i=0; i<allrules.length; i++) { 1862 rule = ''; 1863 if (/^\.mp3flowplayer_.*Color$/.test(allrules[i].selectorText)) { 1864 if (typeof(allrules[i].cssText) != 'undefined') { 1865 rule = allrules[i].cssText; 1866 } else if (typeof(allrules[i].style.cssText) != 'undefined') { 1867 rule = allrules[i].style.cssText; 1868 } 1869 if (rule != '' && /.*color\s*:\s*([^;]+).*/gi.test(rule)) { 1870 rule = rule.replace(/.*color\s*:\s*([^;]+).*/gi, '$1'); 1871 var colprop = allrules[i].selectorText.replace(/^\.mp3flowplayer_/, ''); 1872 controls[colprop] = rule; 1873 } 1874 } 1875 } 1876 allrules = false; 1877 } 1878 1879 for(i=0; i<M.util.audio_players.length; i++) { 1880 var audio = M.util.audio_players[i]; 1881 if (audio.small) { 1882 controls.controlall = false; 1883 controls.height = 15; 1884 controls.time = false; 1885 } else { 1886 controls.controlall = true; 1887 controls.height = 25; 1888 controls.time = true; 1889 } 1890 flowplayer(audio.id, M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.18.swf', { 1891 plugins: {controls: controls, audio: {url: M.cfg.wwwroot + '/lib/flowplayer/flowplayer.audio-3.2.11.swf'}}, 1892 clip: {url: audio.fileurl, provider: "audio", autoPlay: false} 1893 }); 1894 } 1895 } 1896 1897 if (M.cfg.jsrev == -1) { 1898 var jsurl = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.13.js'; 1899 } else { 1900 var jsurl = M.cfg.wwwroot + '/lib/javascript.php?jsfile=/lib/flowplayer/flowplayer-3.2.13.min.js&rev=' + M.cfg.jsrev; 1901 } 1902 var fileref = document.createElement('script'); 1903 fileref.setAttribute('type','text/javascript'); 1904 fileref.setAttribute('src', jsurl); 1905 fileref.onload = embed_function; 1906 fileref.onreadystatechange = embed_function; 1907 document.getElementsByTagName('head')[0].appendChild(fileref); 1908 } 1909 };
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:29:05 2014 | Cross-referenced by PHPXref 0.7.1 |