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