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