MDL-35819 Improve performance of help tooltip
[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,
0e88b7d9
ARN
1416 initialised : false,
1417 setup : function(Y) {
1418 if (this.initialised) {
1419 // Exit early if we have already completed setup
1420 return;
f855a5d2 1421 }
0e88b7d9
ARN
1422 this.Y = Y;
1423 Y.one('body').delegate('click', this.display, 'span.helplink a', this);
1424 this.initialised = true;
2cf81209 1425 },
0e88b7d9
ARN
1426 add : function(Y, properties) {
1427 this.setup(Y);
1428 },
1429 display : function(event) {
2cf81209
SH
1430 event.preventDefault();
1431 if (M.util.help_icon.instance === null) {
1432 var Y = M.util.help_icon.Y;
bbb553e0 1433 Y.use('overlay', 'io-base', 'event-mouseenter', 'node', 'event-key', 'escape', function(Y) {
2cf81209 1434 var help_content_overlay = {
61ddb953 1435 helplink : null,
2cf81209
SH
1436 overlay : null,
1437 init : function() {
1438
bbb553e0 1439 var strclose = Y.Escape.html(M.str.form.close);
bbb553e0 1440 var footerbtn = Y.Node.create('<button class="closebtn">'+strclose+'</button>');
2cf81209
SH
1441 // Create an overlay from markup
1442 this.overlay = new Y.Overlay({
bbb553e0 1443 footerContent: footerbtn,
2cf81209 1444 bodyContent: '',
61ddb953 1445 id: 'helppopupbox',
3a42ad12 1446 width:'400px',
2cf81209 1447 visible : false,
3a42ad12 1448 constrain : true
2cf81209
SH
1449 });
1450 this.overlay.render(Y.one(document.body));
1451
bbb553e0 1452 footerbtn.on('click', this.overlay.hide, this.overlay);
3a42ad12 1453
2cf81209 1454 var boundingBox = this.overlay.get("boundingBox");
2cf81209
SH
1455
1456 // Hide the menu if the user clicks outside of its content
1457 boundingBox.get("ownerDocument").on("mousedown", function (event) {
1458 var oTarget = event.target;
0e88b7d9 1459 var menuButton = this.helplink;
3a42ad12 1460
2cf81209
SH
1461 if (!oTarget.compareTo(menuButton) &&
1462 !menuButton.contains(oTarget) &&
1463 !oTarget.compareTo(boundingBox) &&
1464 !boundingBox.contains(oTarget)) {
1465 this.overlay.hide();
1466 }
1467 }, this);
61ddb953
RW
1468 },
1469
1470 close : function(e) {
1471 e.preventDefault();
1472 this.helplink.focus();
1473 this.overlay.hide();
2cf81209
SH
1474 },
1475
0e88b7d9
ARN
1476 display : function(event) {
1477 var overlayPosition;
1478 this.helplink = event.target.ancestor('span.helplink a', true);
1479 if (Y.one('html').get('dir') === 'rtl') {
1480 overlayPosition = [Y.WidgetPositionAlign.TR, Y.WidgetPositionAlign.LC];
d01c6b26 1481 } else {
0e88b7d9 1482 overlayPosition = [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.RC];
d01c6b26
SC
1483 }
1484
2cf81209 1485 this.overlay.set('bodyContent', Y.Node.create('<img src="'+M.cfg.loadingicon+'" class="spinner" />'));
0e88b7d9 1486 this.overlay.set("align", {node:this.helplink, points: overlayPosition});
2cf81209
SH
1487
1488 var cfg = {
1489 method: 'get',
1490 context : this,
0e88b7d9
ARN
1491 data : {
1492 ajax : 1
1493 },
2cf81209
SH
1494 on: {
1495 success: function(id, o, node) {
1496 this.display_callback(o.responseText);
1497 },
1498 failure: function(id, o, node) {
1499 var debuginfo = o.statusText;
1500 if (M.cfg.developerdebug) {
1501 o.statusText += ' (' + ajaxurl + ')';
1502 }
1503 this.display_callback('bodyContent',debuginfo);
1504 }
1505 }
1506 };
36282d85 1507
0e88b7d9 1508 Y.io(this.helplink.get('href'), cfg);
2cf81209
SH
1509 this.overlay.show();
1510 },
36282d85 1511
2cf81209 1512 display_callback : function(content) {
e4c9460c 1513 content = '<div role="alert">' + content + '</div>';
61ddb953 1514 this.overlay.set('bodyContent', content);
2cf81209
SH
1515 },
1516
1517 hideContent : function() {
1518 help = this;
1519 help.overlay.hide();
36282d85 1520 }
667c0ae1 1521 };
2cf81209
SH
1522 help_content_overlay.init();
1523 M.util.help_icon.instance = help_content_overlay;
0e88b7d9 1524 M.util.help_icon.instance.display(event);
2cf81209
SH
1525 });
1526 } else {
0e88b7d9 1527 M.util.help_icon.instance.display(event);
2cf81209
SH
1528 }
1529 },
3a42ad12
RW
1530 init : function(Y) {
1531 this.Y = Y;
36282d85 1532 }
667c0ae1 1533};
d2dbd0c0
SH
1534
1535/**
1536 * Custom menu namespace
1537 */
1538M.core_custom_menu = {
1539 /**
1540 * This method is used to initialise a custom menu given the id that belongs
1541 * to the custom menu's root node.
3a42ad12 1542 *
d2dbd0c0
SH
1543 * @param {YUI} Y
1544 * @param {string} nodeid
1545 */
1546 init : function(Y, nodeid) {
242b78f7
SH
1547 var node = Y.one('#'+nodeid);
1548 if (node) {
1549 Y.use('node-menunav', function(Y) {
1550 // Get the node
1551 // Remove the javascript-disabled class.... obviously javascript is enabled.
1552 node.removeClass('javascript-disabled');
1553 // Initialise the menunav plugin
1554 node.plug(Y.Plugin.NodeMenuNav);
1555 });
1556 }
d2dbd0c0 1557 }
667c0ae1 1558};
572dd8ec
SH
1559
1560/**
1561 * Used to store form manipulation methods and enhancments
1562 */
1563M.form = M.form || {};
1564
1565/**
1566 * Converts a nbsp indented select box into a multi drop down custom control much
1567 * like the custom menu. It also selectable categories on or off.
1568 *
1569 * $form->init_javascript_enhancement('elementname','smartselect', array('selectablecategories'=>true|false, 'mode'=>'compact'|'spanning'));
1570 *
1571 * @param {YUI} Y
1572 * @param {string} id
1573 * @param {Array} options
1574 */
1575M.form.init_smartselect = function(Y, id, options) {
882e66fe
SH
1576 if (!id.match(/^id_/)) {
1577 id = 'id_'+id;
1578 }
1579 var select = Y.one('select#'+id);
572dd8ec
SH
1580 if (!select) {
1581 return false;
1582 }
1583 Y.use('event-delegate',function(){
1584 var smartselect = {
1585 id : id,
1586 structure : [],
1587 options : [],
1588 submenucount : 0,
1589 currentvalue : null,
1590 currenttext : null,
1591 shownevent : null,
1592 cfg : {
1593 selectablecategories : true,
1594 mode : null
1595 },
1596 nodes : {
1597 select : null,
1598 loading : null,
1599 menu : null
1600 },
1601 init : function(Y, id, args, nodes) {
1602 if (typeof(args)=='object') {
1603 for (var i in this.cfg) {
1604 if (args[i] || args[i]===false) {
1605 this.cfg[i] = args[i];
1606 }
1607 }
1608 }
3a42ad12 1609
572dd8ec
SH
1610 // Display a loading message first up
1611 this.nodes.select = nodes.select;
1612
1613 this.currentvalue = this.nodes.select.get('selectedIndex');
1614 this.currenttext = this.nodes.select.all('option').item(this.currentvalue).get('innerHTML');
1615
1616 var options = Array();
667c0ae1 1617 options[''] = {text:this.currenttext,value:'',depth:0,children:[]};
572dd8ec
SH
1618 this.nodes.select.all('option').each(function(option, index) {
1619 var rawtext = option.get('innerHTML');
1620 var text = rawtext.replace(/^(&nbsp;)*/, '');
1621 if (rawtext === text) {
1622 text = rawtext.replace(/^(\s)*/, '');
1623 var depth = (rawtext.length - text.length ) + 1;
1624 } else {
1625 var depth = ((rawtext.length - text.length )/12)+1;
1626 }
1627 option.set('innerHTML', text);
1628 options['i'+index] = {text:text,depth:depth,index:index,children:[]};
1629 }, this);
1630
1631 this.structure = [];
1632 var structcount = 0;
1633 for (var i in options) {
1634 var o = options[i];
1635 if (o.depth == 0) {
1636 this.structure.push(o);
1637 structcount++;
1638 } else {
1639 var d = o.depth;
1640 var current = this.structure[structcount-1];
1641 for (var j = 0; j < o.depth-1;j++) {
1642 if (current && current.children) {
1643 current = current.children[current.children.length-1];
1644 }
1645 }
1646 if (current && current.children) {
1647 current.children.push(o);
1648 }
1649 }
1650 }
1651
1652 this.nodes.menu = Y.Node.create(this.generate_menu_content());
1653 this.nodes.menu.one('.smartselect_mask').setStyle('opacity', 0.01);
1654 this.nodes.menu.one('.smartselect_mask').setStyle('width', (this.nodes.select.get('offsetWidth')+5)+'px');
1655 this.nodes.menu.one('.smartselect_mask').setStyle('height', (this.nodes.select.get('offsetHeight'))+'px');
1656
1657 if (this.cfg.mode == null) {
1658 var formwidth = this.nodes.select.ancestor('form').get('offsetWidth');
1659 if (formwidth < 400 || this.nodes.menu.get('offsetWidth') < formwidth*2) {
1660 this.cfg.mode = 'compact';
1661 } else {
1662 this.cfg.mode = 'spanning';
1663 }
1664 }
1665
1666 if (this.cfg.mode == 'compact') {
1667 this.nodes.menu.addClass('compactmenu');
1668 } else {
1669 this.nodes.menu.addClass('spanningmenu');
1670 this.nodes.menu.delegate('mouseover', this.show_sub_menu, '.smartselect_submenuitem', this);
1671 }
1672
1673 Y.one(document.body).append(this.nodes.menu);
1674 var pos = this.nodes.select.getXY();
1675 pos[0] += 1;
1676 this.nodes.menu.setXY(pos);
1677 this.nodes.menu.on('click', this.handle_click, this);
32690ac2
SH
1678
1679 Y.one(window).on('resize', function(){
1680 var pos = this.nodes.select.getXY();
1681 pos[0] += 1;
1682 this.nodes.menu.setXY(pos);
1683 }, this);
572dd8ec
SH
1684 },
1685 generate_menu_content : function() {
1686 var content = '<div id="'+this.id+'_smart_select" class="smartselect">';
1687 content += this.generate_submenu_content(this.structure[0], true);
1688 content += '</ul></div>';
1689 return content;
1690 },
1691 generate_submenu_content : function(item, rootelement) {
1692 this.submenucount++;
1693 var content = '';
1694 if (item.children.length > 0) {
1695 if (rootelement) {
1696 content += '<div class="smartselect_mask" href="#ss_submenu'+this.submenucount+'">&nbsp;</div>';
1697 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_menu">';
1698 content += '<div class="smartselect_menu_content">';
1699 } else {
1700 content += '<li class="smartselect_submenuitem">';
1701 var categoryclass = (this.cfg.selectablecategories)?'selectable':'notselectable';
1702 content += '<a class="smartselect_menuitem_label '+categoryclass+'" href="#ss_submenu'+this.submenucount+'" value="'+item.index+'">'+item.text+'</a>';
1703 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_submenu">';
1704 content += '<div class="smartselect_submenu_content">';
1705 }
1706 content += '<ul>';
1707 for (var i in item.children) {
1708 content += this.generate_submenu_content(item.children[i],false);
1709 }
1710 content += '</ul>';
1711 content += '</div>';
1712 content += '</div>';
1713 if (rootelement) {
1714 } else {
1715 content += '</li>';
1716 }
1717 } else {
1718 content += '<li class="smartselect_menuitem">';
1719 content += '<a class="smartselect_menuitem_content selectable" href="#" value="'+item.index+'">'+item.text+'</a>';
1720 content += '</li>';
1721 }
1722 return content;
1723 },
1724 select : function(e) {
1725 var t = e.target;
1726 e.halt();
1727 this.currenttext = t.get('innerHTML');
1728 this.currentvalue = t.getAttribute('value');
1729 this.nodes.select.set('selectedIndex', this.currentvalue);
1730 this.hide_menu();
1731 },
1732 handle_click : function(e) {
1733 var target = e.target;
1734 if (target.hasClass('smartselect_mask')) {
1735 this.show_menu(e);
1736 } else if (target.hasClass('selectable') || target.hasClass('smartselect_menuitem')) {
1737 this.select(e);
1738 } else if (target.hasClass('smartselect_menuitem_label') || target.hasClass('smartselect_submenuitem')) {
1739 this.show_sub_menu(e);
1740 }
1741 },
1742 show_menu : function(e) {
1743 e.halt();
1744 var menu = e.target.ancestor().one('.smartselect_menu');
1745 menu.addClass('visible');
1746 this.shownevent = Y.one(document.body).on('click', this.hide_menu, this);
1747 },
1748 show_sub_menu : function(e) {
1749 e.halt();
1750 var target = e.target;
1751 if (!target.hasClass('smartselect_submenuitem')) {
1752 target = target.ancestor('.smartselect_submenuitem');
1753 }
1754 if (this.cfg.mode == 'compact' && target.one('.smartselect_submenu').hasClass('visible')) {
1755 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1756 return;
1757 }
1758 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1759 target.one('.smartselect_submenu').addClass('visible');
1760 },
1761 hide_menu : function() {
1762 this.nodes.menu.all('.visible').removeClass('visible');
1763 if (this.shownevent) {
1764 this.shownevent.detach();
1765 }
1766 }
667c0ae1 1767 };
572dd8ec
SH
1768 smartselect.init(Y, id, options, {select:select});
1769 });
667c0ae1 1770};
3a42ad12 1771
fcd2cbaf
PS
1772/** List of flv players to be loaded */
1773M.util.video_players = [];
1774/** List of mp3 players to be loaded */
1775M.util.audio_players = [];
1776
1777/**
1778 * Add video player
1779 * @param id element id
1780 * @param fileurl media url
1781 * @param width
1782 * @param height
1783 * @param autosize true means detect size from media
1784 */
1785M.util.add_video_player = function (id, fileurl, width, height, autosize) {
1786 M.util.video_players.push({id: id, fileurl: fileurl, width: width, height: height, autosize: autosize, resized: false});
667c0ae1 1787};
3a42ad12 1788
fcd2cbaf
PS
1789/**
1790 * Add audio player.
1791 * @param id
1792 * @param fileurl
1793 * @param small
1794 */
1795M.util.add_audio_player = function (id, fileurl, small) {
1796 M.util.audio_players.push({id: id, fileurl: fileurl, small: small});
667c0ae1 1797};
3a42ad12 1798
fcd2cbaf
PS
1799/**
1800 * Initialise all audio and video player, must be called from page footer.
1801 */
1802M.util.load_flowplayer = function() {
1803 if (M.util.video_players.length == 0 && M.util.audio_players.length == 0) {
1804 return;
1805 }
1806 if (typeof(flowplayer) == 'undefined') {
1807 var loaded = false;
1808
1809 var embed_function = function() {
1810 if (loaded || typeof(flowplayer) == 'undefined') {
1811 return;
3a42ad12 1812 }
fcd2cbaf
PS
1813 loaded = true;
1814
1815 var controls = {
1816 autoHide: true
1817 }
1818 /* TODO: add CSS color overrides for the flv flow player */
1819
1820 for(var i=0; i<M.util.video_players.length; i++) {
1821 var video = M.util.video_players[i];
1822 if (video.width > 0 && video.height > 0) {
afd1c47c 1823 var src = {src: M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.9.swf', width: video.width, height: video.height};
fcd2cbaf 1824 } else {
afd1c47c 1825 var src = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.9.swf';
fcd2cbaf
PS
1826 }
1827 flowplayer(video.id, src, {
1828 plugins: {controls: controls},
1829 clip: {
1830 url: video.fileurl, autoPlay: false, autoBuffering: true, scaling: 'fit', mvideo: video,
1831 onMetaData: function(clip) {
1832 if (clip.mvideo.autosize && !clip.mvideo.resized) {
1833 clip.mvideo.resized = true;
1834 //alert("metadata!!! "+clip.width+' '+clip.height+' '+JSON.stringify(clip.metaData));
1835 if (typeof(clip.metaData.width) == 'undefined' || typeof(clip.metaData.height) == 'undefined') {
1836 // bad luck, we have to guess - we may not get metadata at all
1837 var width = clip.width;
1838 var height = clip.height;
1839 } else {
1840 var width = clip.metaData.width;
1841 var height = clip.metaData.height;
1842 }
1843 var minwidth = 300; // controls are messed up in smaller objects
1844 if (width < minwidth) {
1845 height = (height * minwidth) / width;
1846 width = minwidth;
1847 }
1848
1849 var object = this._api();
1850 object.width = width;
1851 object.height = height;
1852 }
1853 }
1854 }
1855 });
1856 }
1857 if (M.util.audio_players.length == 0) {
1858 return;
1859 }
1860 var controls = {
1861 autoHide: false,
1862 fullscreen: false,
1863 next: false,
1864 previous: false,
1865 scrubber: true,
1866 play: true,
1867 pause: true,
1868 volume: true,
1869 mute: false,
1870 backgroundGradient: [0.5,0,0.3]
1871 };
1872
1873 var rule;
1874 for (var j=0; j < document.styleSheets.length; j++) {
e52f8225
DM
1875
1876 // To avoid javascript security violation accessing cross domain stylesheets
1877 var allrules = false;
1878 try {
1879 if (typeof (document.styleSheets[j].rules) != 'undefined') {
1880 allrules = document.styleSheets[j].rules;
1881 } else if (typeof (document.styleSheets[j].cssRules) != 'undefined') {
1882 allrules = document.styleSheets[j].cssRules;
1883 } else {
1884 // why??
1885 continue;
1886 }
1887 } catch (e) {
fcd2cbaf
PS
1888 continue;
1889 }
e52f8225
DM
1890
1891 // On cross domain style sheets Chrome V8 allows access to rules but returns null
1892 if (!allrules) {
1893 continue;
1894 }
1895
fcd2cbaf
PS
1896 for(var i=0; i<allrules.length; i++) {
1897 rule = '';
1898 if (/^\.mp3flowplayer_.*Color$/.test(allrules[i].selectorText)) {
1899 if (typeof(allrules[i].cssText) != 'undefined') {
18382a85 1900 rule = allrules[i].cssText;
fcd2cbaf
PS
1901 } else if (typeof(allrules[i].style.cssText) != 'undefined') {
1902 rule = allrules[i].style.cssText;
1903 }
1904 if (rule != '' && /.*color\s*:\s*([^;]+).*/gi.test(rule)) {
1905 rule = rule.replace(/.*color\s*:\s*([^;]+).*/gi, '$1');
1906 var colprop = allrules[i].selectorText.replace(/^\.mp3flowplayer_/, '');
1907 controls[colprop] = rule;
1908 }
1909 }
1910 }
1911 allrules = false;
1912 }
1913
1914 for(i=0; i<M.util.audio_players.length; i++) {
1915 var audio = M.util.audio_players[i];
1916 if (audio.small) {
1917 controls.controlall = false;
1918 controls.height = 15;
1919 controls.time = false;
1920 } else {
1921 controls.controlall = true;
1922 controls.height = 25;
1923 controls.time = true;
1924 }
afd1c47c
PS
1925 flowplayer(audio.id, M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.9.swf', {
1926 plugins: {controls: controls, audio: {url: M.cfg.wwwroot + '/lib/flowplayer/flowplayer.audio-3.2.8.swf'}},
fcd2cbaf
PS
1927 clip: {url: audio.fileurl, provider: "audio", autoPlay: false}
1928 });
1929 }
1930 }
1931
1932 if (M.cfg.jsrev == -10) {
afd1c47c 1933 var jsurl = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.8.min.js';
fcd2cbaf 1934 } else {
18ac11b7 1935 var jsurl = M.cfg.wwwroot + '/lib/javascript.php?jsfile=/lib/flowplayer/flowplayer-3.2.8.min.js&rev=' + M.cfg.jsrev;
fcd2cbaf
PS
1936 }
1937 var fileref = document.createElement('script');
1938 fileref.setAttribute('type','text/javascript');
1939 fileref.setAttribute('src', jsurl);
1940 fileref.onload = embed_function;
1941 fileref.onreadystatechange = embed_function;
1942 document.getElementsByTagName('head')[0].appendChild(fileref);
1943 }
00e8d08d 1944};